Merge "Revert "Allow collection type converters in query parameters"" into oc-mr1-support-27.0-dev
am: 7fa8eb8a64

Change-Id: I73cce00067d793ca7bbe9f63c8bbf926d8fdd77c
diff --git a/annotations/Android.mk b/annotations/Android.mk
index 41abfaa..684a77a 100644
--- a/annotations/Android.mk
+++ b/annotations/Android.mk
@@ -15,11 +15,9 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-annotations
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/annotations/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/IntDef.java b/annotations/src/main/java/android/support/annotation/IntDef.java
index f621b7f..9945726 100644
--- a/annotations/src/main/java/android/support/annotation/IntDef.java
+++ b/annotations/src/main/java/android/support/annotation/IntDef.java
@@ -46,12 +46,14 @@
  *      flag = true,
  *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
  * </code></pre>
+ *
+ * @see LongDef
  */
 @Retention(SOURCE)
 @Target({ANNOTATION_TYPE})
 public @interface IntDef {
     /** Defines the allowed constants for this element */
-    long[] value() default {};
+    int[] value() default {};
 
     /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
     boolean flag() default false;
diff --git a/annotations/src/main/java/android/support/annotation/LongDef.java b/annotations/src/main/java/android/support/annotation/LongDef.java
new file mode 100644
index 0000000..3dea338
--- /dev/null
+++ b/annotations/src/main/java/android/support/annotation/LongDef.java
@@ -0,0 +1,60 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated long element represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the LongDef#flag() attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * Example:
+ * <pre><code>
+ *  &#64;Retention(SOURCE)
+ *  &#64;LongDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
+ *  public static final long NAVIGATION_MODE_STANDARD = 0;
+ *  public static final long NAVIGATION_MODE_LIST = 1;
+ *  public static final long NAVIGATION_MODE_TABS = 2;
+ *  ...
+ *  public abstract void setNavigationMode(@NavigationMode long mode);
+ *  &#64;NavigationMode
+ *  public abstract long getNavigationMode();
+ * </code></pre>
+ * For a flag, set the flag attribute:
+ * <pre><code>
+ *  &#64;LongDef(
+ *      flag = true,
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
+ *
+ * @see IntDef
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface LongDef {
+    /** Defines the allowed constants for this element */
+    long[] value() default {};
+
+    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+    boolean flag() default false;
+}
\ 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/build.gradle b/build.gradle
index ee15ba2..7bbeb56 100644
--- a/build.gradle
+++ b/build.gradle
@@ -26,6 +26,7 @@
     dependencies {
         classpath build_libs.gradle
         classpath build_libs.jacoco
+        classpath build_libs.jetifier
     }
 }
 
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 02e1411..d68b769 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -29,6 +29,7 @@
 build_libs.jarjar_gradle = 'org.anarres.jarjar:jarjar-gradle:1.0.0'
 build_libs.error_prone = 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13'
 build_libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
+build_libs.jetifier = 'androidx.tools.jetifier:gradle-plugin:0.1'
 build_libs.kotlin = [gradle_plugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0"]
 // jdiff dependencies
 build_libs.xml_parser_apis = 'xerces:xmlParserAPIs:2.6.2'
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index cd12561..fe664e0 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -16,8 +16,9 @@
 // Add ext.libs for library versions
 def libs = [:]
 
-libs.exclude_annotations = {
+libs.exclude_annotations_transitive = {
     exclude module: 'support-annotations'
+    transitive = true
 }
 
 libs.exclude_annotations_transitive = {
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index d47d5e8..ee2c1cc 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -61,7 +61,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
@@ -147,8 +147,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/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
index a4fd632..8b94cd7 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
@@ -77,6 +79,26 @@
         project.apply(mapOf("plugin" to "com.android.library"))
         project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
 
+        project.configurations.all { configuration ->
+            if (isCoreSupportLibrary && project.name != "support-annotations") {
+                // While this usually happens naturally due to normal project dependencies, force
+                // evaluation on the annotations project in case the below substitution is the only
+                // dependency to this project. See b/70650240 on what happens when this is missing.
+                project.evaluationDependsOn(":support-annotations")
+
+                // 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")
 
@@ -122,12 +144,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/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
index f9ee8b3..48103d2 100644
--- a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
@@ -30,6 +30,7 @@
 const val JSR250 = "javax.annotation:javax.annotation-api:1.2"
 const val JUNIT = "junit:junit:4.12"
 const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:1.2.0"
+const val LINT = "com.android.tools.lint:lint:26.0.0"
 const val MOCKITO_CORE = "org.mockito:mockito-core:2.7.6"
 const val MULTIDEX = "com.android.support:multidex:1.0.1"
 const val REACTIVE_STREAMS = "org.reactivestreams:reactive-streams:1.0.0"
diff --git a/car/Android.mk b/car/Android.mk
new file mode 100644
index 0000000..18f08e4
--- /dev/null
+++ b/car/Android.mk
@@ -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.
+
+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_JAVA_LIBRARIES := \
+        android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+        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..7ea0873
--- /dev/null
+++ b/car/build.gradle
@@ -0,0 +1,39 @@
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+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 {
+    sourceSets {
+        main.res.srcDirs 'res', 'res-public'
+    }
+}
+
+supportLibrary {
+    name = "Android Car Support UI"
+    publish = false
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SUPPORT
+    inceptionYear = "2017"
+    description = "Android Car Support UI"
+    java8Library = true
+    legacySourceLocation = true
+    minSdkVersion = 24
+}
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..5d01c59
--- /dev/null
+++ b/car/res/layout/car_paged_list_item_content.xml
@@ -0,0 +1,103 @@
+<?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..e7b1f61
--- /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">
+
+    <android.support.v7.widget.RecyclerView
+        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..6f3287b
--- /dev/null
+++ b/car/res/layout/car_paged_scrollbar_buttons.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: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: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="@dimen/car_min_scroll_bar_thumb_height"
+            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: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..12eb594
--- /dev/null
+++ b/car/res/values-night/colors.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.
+  -->
+<resources>
+    <color name="car_title">@color/car_title_light</color>
+    <color name="car_title2">@color/car_title2_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..630d0d6
--- /dev/null
+++ b/car/res/values/attrs.xml
@@ -0,0 +1,91 @@
+<?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">
+        <!-- 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..5b90125
--- /dev/null
+++ b/car/res/values/colors.xml
@@ -0,0 +1,164 @@
+<?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_title2_light">@color/car_grey_100</color>
+    <color name="car_title2_dark">@color/car_grey_900</color>
+    <color name="car_title2">@color/car_title2_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..606bdc5
--- /dev/null
+++ b/car/res/values/styles.xml
@@ -0,0 +1,152 @@
+<?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 title2 text. The color of this text changes based on day/night mode. -->
+    <style name="CarTitle2">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_title2_size</item>
+        <item name="android:textColor">@color/car_title2</item>
+    </style>
+
+    <!-- Title2 text that is permanently a dark color. -->
+    <style name="CarTitle2.Dark">
+        <item name="android:textColor">@color/car_title2_dark</item>
+    </style>
+
+    <!-- Title2 text that is permanently a light color. -->
+    <style name="CarTitle2.Light">
+        <item name="android:textColor">@color/car_title2_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..9769142
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
@@ -0,0 +1,163 @@
+/*
+ * 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 calling {@link #getDrawerController()}.
+ *       {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)}.
+ *   <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 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);
+        CarDrawerAdapter rootAdapter = getRootAdapter();
+        if (rootAdapter != null) {
+            mDrawerController.setRootAdapter(rootAdapter);
+        }
+
+        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.
+     * @deprecated Do not implement this, instead call {@link #getDrawerController}.
+     * {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} directly.
+     */
+    @Deprecated
+    @Nullable
+    protected CarDrawerAdapter getRootAdapter() {
+        return null;
+    }
+
+    /**
+     * 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/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..08cc48e
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItem.java
@@ -0,0 +1,725 @@
+/*
+ * 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() {
+            // Set all relevant fields in layout params to avoid carried over params when the item
+            // gets bound to a recycled view holder.
+            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.removeRule(RelativeLayout.CENTER_VERTICAL);
+                            layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
+                                    R.dimen.car_padding_4);
+                        } else {
+                            // Centered vertically.
+                            layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+                            layoutParams.topMargin = 0;
+                        }
+                        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);
+                        layoutParams.topMargin = 0;
+
+                        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() {
+            // Set all relevant fields in layout params to avoid carried over params when the item
+            // gets bound to a recycled view holder.
+            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);
+                    layoutParams.topMargin = 0;
+                    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.removeRule(RelativeLayout.BELOW);
+                    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.removeRule(RelativeLayout.CENTER_VERTICAL);
+                    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.removeRule(RelativeLayout.CENTER_VERTICAL);
+                    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);
+                        vh.getSupplementalIcon().setClickable(
+                                mSupplementalIconOnClickListener != null);
+                    });
+                    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.");
+            }
+            if (listener == null) {
+                throw new IllegalArgumentException("Action OnClickListener cannot be null.");
+            }
+            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) || TextUtils.isEmpty(action2Text)) {
+                throw new IllegalArgumentException("Action text cannot be empty.");
+            }
+            if (action1OnClickListener == null || action2OnClickListener == null) {
+                throw new IllegalArgumentException("Action OnClickListener cannot be null.");
+            }
+            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/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
new file mode 100644
index 0000000..4e960d4
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -0,0 +1,1047 @@
+/*
+ * 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.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.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.support.annotation.ColorRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.LinearLayoutManager;
+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.View;
+import android.widget.FrameLayout;
+
+import androidx.car.R;
+
+/**
+ * View that wraps a {@link android.support.v7.widget.RecyclerView} and a scroll bar that has
+ * page up and down arrows. Interaction with this view is similar to a {@code RecyclerView} as it
+ * takes the same adapter.
+ *
+ * <p>By default, this PagedListView utilizes a vertical {@link LinearLayoutManager} to display
+ * its items.
+ */
+public class PagedListView extends FrameLayout {
+    /**
+     * The key used to save the state of this PagedListView's super class in
+     * {@link #onSaveInstanceState()}.
+     */
+    private static final String SAVED_SUPER_STATE_KEY = "PagedListViewSuperState";
+
+    /**
+     * The key used to save the state of {@link #mRecyclerView} so that it can be restored
+     * on configuration change. The actual saving of state will be controlled by the LayoutManager
+     * of the RecyclerView; this value simply ensures the state is passed on to the LayoutManager.
+     */
+    private static final String SAVED_RECYCLER_VIEW_STATE_KEY = "RecyclerViewState";
+
+    /** 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.
+     */
+    private static final int PAGINATION_HOLD_DELAY_MS = 400;
+
+    /**
+     * A fling distance to use when the up button is pressed. This value is arbitrary and just needs
+     * to be large enough so that the maximum amount of fling is applied. The
+     * {@link PagedSnapHelper} will handle capping this value so that the RecyclerView is scrolled
+     * one page upwards.
+     */
+    private static final int FLING_UP_DISTANCE = -10000;
+
+    /**
+     * A fling distance to use when the down button is pressed. This value is arbitrary and just
+     * needs to be large enough so that the maximum amount of fling is applied. The
+     * {@link PagedSnapHelper} will handle capping this value so that the RecyclerView is scrolled
+     * one page downwards.
+     */
+    private static final int FLING_DOWN_DISTANCE = 10000;
+
+    private static final String TAG = "PagedListView";
+    private static final int INVALID_RESOURCE_ID = -1;
+
+    private final RecyclerView mRecyclerView;
+    private final PagedSnapHelper mSnapHelper;
+    private final Handler mHandler = new Handler();
+    private final boolean mScrollBarEnabled;
+    @VisibleForTesting
+    final PagedScrollBarView mScrollBarView;
+
+    private int mRowsPerPage = -1;
+    private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
+
+    /** Maximum number of pages to show. */
+    private int mMaxPages;
+
+    private 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;
+
+    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 = findViewById(R.id.recycler_view);
+
+        mMaxPages = getDefaultMaxPages();
+
+        RecyclerView.LayoutManager layoutManager =
+                new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
+        mRecyclerView.setLayoutManager(layoutManager);
+
+        mSnapHelper = new PagedSnapHelper();
+        mSnapHelper.attachToRecyclerView(mRecyclerView);
+
+        mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
+        mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
+
+        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(direction -> {
+            switch (direction) {
+                case PagedScrollBarView.PaginationListener.PAGE_UP:
+                    pageUp();
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrollUpButtonClicked();
+                    }
+                    break;
+                case PagedScrollBarView.PaginationListener.PAGE_DOWN:
+                    pageDown();
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrollDownButtonClicked();
+                    }
+                    break;
+                default:
+                    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);
+    }
+
+    /**
+     * 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
+                || mRecyclerView.getLayoutManager() == null) {
+            return -1;
+        }
+        return mRecyclerView.getLayoutManager().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 RecyclerView 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) {
+        if (mRecyclerView.getLayoutManager() == null) {
+            return;
+        }
+
+        PagedSmoothScroller smoothScroller = new PagedSmoothScroller(getContext());
+        smoothScroller.setTargetPosition(position);
+
+        mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
+
+        // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
+        // the pagination arrows actually get updated. See 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();
+    }
+
+    @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();
+    }
+
+    /**
+     * 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();
+    }
+
+    /**
+     * Sets the color of scrollbar.
+     *
+     * <p>Custom color ignores {@link DayNightStyle}. Calling {@link #resetScrollbarColor} resets to
+     * default color.
+     *
+     * @param color Resource identifier of the color.
+     */
+    public void setScrollbarColor(@ColorRes int color) {
+        mScrollBarView.setThumbColor(color);
+    }
+
+    /**
+     * Resets the color of scrollbar to default.
+     */
+    public void resetScrollbarColor() {
+        mScrollBarView.resetThumbColor();
+    }
+
+    /**
+     * 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();
+            }
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /** 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;
+    }
+
+    /** Scrolls the contents of the RecyclerView up a page. */
+    private void pageUp() {
+        mRecyclerView.fling(0, FLING_UP_DISTANCE);
+    }
+
+    /** Scrolls the contents of the RecyclerView down a page. */
+    private void pageDown() {
+        mRecyclerView.fling(0, FLING_DOWN_DISTANCE);
+    }
+
+    /**
+     * 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 */
+    private 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.
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+
+        if (layoutManager == null) {
+            return;
+        }
+
+        // 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 = layoutManager.getFocusedChild();
+        View firstBorn = layoutManager.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*/);
+    }
+
+    /**
+     * 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
+     */
+    private void updatePaginationButtons(boolean animate) {
+        if (!mScrollBarEnabled) {
+            // Don't change the visibility of the ScrollBar unless it's enabled.
+            return;
+        }
+
+        boolean isAtStart = isAtStart();
+        boolean isAtEnd = isAtEnd();
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+
+        if ((isAtStart && isAtEnd) || layoutManager == null || layoutManager.getItemCount() == 0) {
+            mScrollBarView.setVisibility(View.INVISIBLE);
+        } else {
+            mScrollBarView.setVisibility(View.VISIBLE);
+        }
+        mScrollBarView.setUpEnabled(!isAtStart);
+        mScrollBarView.setDownEnabled(!isAtEnd);
+
+        if (layoutManager == null) {
+            return;
+        }
+
+        if (mRecyclerView.getLayoutManager().canScrollVertically()) {
+            mScrollBarView.setParameters(
+                    mRecyclerView.computeVerticalScrollRange(),
+                    mRecyclerView.computeVerticalScrollOffset(),
+                    mRecyclerView.computeHorizontalScrollExtent(), animate);
+        } else {
+            mScrollBarView.setParameters(
+                    mRecyclerView.computeHorizontalScrollRange(),
+                    mRecyclerView.computeHorizontalScrollOffset(),
+                    mRecyclerView.computeHorizontalScrollRange(), animate);
+        }
+
+        invalidate();
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+    public boolean isAtStart() {
+        return mSnapHelper.isAtStart(mRecyclerView.getLayoutManager());
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    public boolean isAtEnd() {
+        return mSnapHelper.isAtEnd(mRecyclerView.getLayoutManager());
+    }
+
+    @UiThread
+    private 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);
+        }
+    }
+
+    private int calculateMaxItemCount() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null) {
+            return -1;
+        }
+
+        View firstChild = layoutManager.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.
+     */
+    private void updateRowsPerPage() {
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null) {
+            mRowsPerPage = 1;
+            return;
+        }
+
+        View firstChild = layoutManager.getChildAt(0);
+        if (firstChild == null || firstChild.getHeight() == 0) {
+            mRowsPerPage = 1;
+        } else {
+            mRowsPerPage = Math.max(1, (getHeight() - getPaddingTop()) / firstChild.getHeight());
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(SAVED_SUPER_STATE_KEY, super.onSaveInstanceState());
+
+        SparseArray<Parcelable> recyclerViewState = new SparseArray<>();
+        mRecyclerView.saveHierarchyState(recyclerViewState);
+        bundle.putSparseParcelableArray(SAVED_RECYCLER_VIEW_STATE_KEY, recyclerViewState);
+
+        return bundle;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof Bundle)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        Bundle bundle = (Bundle) state;
+        mRecyclerView.restoreHierarchyState(
+                bundle.getSparseParcelableArray(SAVED_RECYCLER_VIEW_STATE_KEY));
+
+        super.onRestoreInstanceState(bundle.getParcelable(SAVED_SUPER_STATE_KEY));
+    }
+
+    @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 is. 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);
+    }
+
+    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 (!isAtStart() && isAtEnd()) {
+                            mOnScrollListener.onReachBottom();
+                        }
+                    }
+                    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);
+                    }
+                }
+            };
+
+    private final Runnable mPaginationRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    boolean upPressed = mScrollBarView.isUpPressed();
+                    boolean downPressed = mScrollBarView.isDownPressed();
+                    if (upPressed && downPressed) {
+                        return;
+                    }
+                    if (upPressed) {
+                        pageUp();
+                    } else if (downPressed) {
+                        pageDown();
+                    }
+                }
+            };
+
+    private final Runnable mUpdatePaginationRunnable =
+            () -> 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 scroll up button is clicked */
+        public void onScrollUpButtonClicked() {}
+        /** Called when scroll down button is clicked */
+        public void onScrollDownButtonClicked() {}
+
+        /**
+         * 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) {}
+    }
+
+    /**
+     * 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 mItemSpacing;
+
+        private ItemSpacingDecoration(int itemSpacing) {
+            mItemSpacing = itemSpacing;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            int position = parent.getChildAdapterPosition(view);
+
+            // Skip offset for last item.
+            if (position == state.getItemCount() - 1) {
+                return;
+            }
+
+            outRect.bottom = mItemSpacing;
+        }
+
+        /**
+         * @param itemSpacing sets spacing between each item.
+         */
+        public void setItemSpacing(int itemSpacing) {
+            mItemSpacing = itemSpacing;
+        }
+    }
+
+    /**
+     * 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;
+                }
+
+                Rect containerRect = new Rect();
+                container.getGlobalVisibleRect(containerRect);
+
+                Rect startRect = new Rect();
+                startChild.getGlobalVisibleRect(startRect);
+
+                Rect endRect = new Rect();
+                endChild.getGlobalVisibleRect(endRect);
+
+                int left = container.getLeft() + mDividerStartMargin
+                        + (startRect.left - containerRect.left);
+                int right = container.getRight() - (endRect.right - containerRect.right);
+                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..1855f2b
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -0,0 +1,300 @@
+/*
+ * 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.annotation.ColorRes;
+import android.support.annotation.VisibleForTesting;
+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;
+    @VisibleForTesting
+    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 boolean mUseCustomThumbBackground;
+    @ColorRes private int mCustomThumbBackgroundResId;
+    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) {
+        // If the scroll bars aren't visible, then no need to update.
+        if (getVisibility() == View.GONE || range == 0) {
+            return;
+        }
+
+        // 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.
+        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();
+    }
+
+    /**
+     * Sets the color of thumb.
+     *
+     * <p>Custom thumb color ignores {@link DayNightStyle}. Calling {@link #resetThumbColor} resets
+     * to default color.
+     *
+     * @param color Resource identifier of the color.
+     */
+    public void setThumbColor(@ColorRes int color) {
+        mUseCustomThumbBackground = true;
+        mCustomThumbBackgroundResId = color;
+        reloadColors();
+    }
+
+    /**
+     * Resets the color of thumb to default.
+     */
+    public void resetThumbColor() {
+        mUseCustomThumbBackground = false;
+        reloadColors();
+    }
+
+    /** Reload the colors for the current {@link DayNightStyle}. */
+    private void reloadColors() {
+        int tintResId;
+        int thumbBackgroundResId;
+        int upDownBackgroundResId;
+
+        switch (mDayNightStyle) {
+            case DayNightStyle.AUTO:
+                tintResId = R.color.car_tint;
+                thumbBackgroundResId = R.color.car_scrollbar_thumb;
+                upDownBackgroundResId = R.drawable.car_pagination_background;
+                break;
+            case DayNightStyle.AUTO_INVERSE:
+                tintResId = R.color.car_tint_inverse;
+                thumbBackgroundResId = R.color.car_scrollbar_thumb_inverse;
+                upDownBackgroundResId = R.drawable.car_pagination_background_inverse;
+                break;
+            case DayNightStyle.FORCE_NIGHT:
+                tintResId = R.color.car_tint_light;
+                thumbBackgroundResId = R.color.car_scrollbar_thumb_light;
+                upDownBackgroundResId = R.drawable.car_pagination_background_night;
+                break;
+            case DayNightStyle.FORCE_DAY:
+                tintResId =  R.color.car_tint_dark;
+                thumbBackgroundResId = R.color.car_scrollbar_thumb_dark;
+                upDownBackgroundResId = R.drawable.car_pagination_background_day;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown DayNightStyle: " + mDayNightStyle);
+        }
+
+        if (mUseCustomThumbBackground) {
+            thumbBackgroundResId = mCustomThumbBackgroundResId;
+        }
+        mScrollThumb.setBackgroundColor(ContextCompat.getColor(getContext(), thumbBackgroundResId));
+
+        int tint = ContextCompat.getColor(getContext(), tintResId);
+        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/src/main/java/androidx/car/widget/PagedSmoothScroller.java b/car/src/main/java/androidx/car/widget/PagedSmoothScroller.java
new file mode 100644
index 0000000..cbd7862
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedSmoothScroller.java
@@ -0,0 +1,74 @@
+/*
+ * 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.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * Custom {@link LinearSmoothScroller} that has:
+ *
+ * <ul>
+ * <li>Custom control over the speed of scrolls.
+ * <li>Scrolling that snaps to start of a child view.
+ * </ul>
+ */
+public final class PagedSmoothScroller extends LinearSmoothScroller {
+    private static final float MILLISECONDS_PER_INCH = 150f;
+    private static final float DECELERATION_TIME_DIVISOR = 0.45f;
+
+    private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
+
+    public PagedSmoothScroller(Context context) {
+        super(context);
+    }
+
+    @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 SNAP_TO_START;
+    }
+
+    @Override
+    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+        int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+        if (dy == 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);
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/PagedSnapHelper.java b/car/src/main/java/androidx/car/widget/PagedSnapHelper.java
new file mode 100644
index 0000000..ad1c710
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedSnapHelper.java
@@ -0,0 +1,269 @@
+/*
+ * 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.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearSnapHelper;
+import android.support.v7.widget.OrientationHelper;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * Extension of a {@link LinearSnapHelper} that will snap to the start of the target child view to
+ * the start of the attached {@link RecyclerView}. The start of the view is defined as the top
+ * if the RecyclerView is scrolling vertically; it is defined as the left (or right if RTL) if the
+ * RecyclerView is scrolling horizontally.
+ */
+public class PagedSnapHelper extends LinearSnapHelper {
+    /**
+     * The percentage of a View that needs to be completely visible for it to be a viable snap
+     * target.
+     */
+    private static final float VIEW_VISIBLE_THRESHOLD = 0.5f;
+
+    private RecyclerView mRecyclerView;
+
+    // Orientation helpers are lazily created per LayoutManager.
+    @Nullable
+    private OrientationHelper mVerticalHelper;
+
+    @Nullable
+    private OrientationHelper mHorizontalHelper;
+
+    @Override
+    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
+            @NonNull View targetView) {
+        int[] out = new int[2];
+
+        out[0] = layoutManager.canScrollHorizontally()
+                ? getHorizontalHelper(layoutManager).getDecoratedStart(targetView)
+                : 0;
+
+        out[1] = layoutManager.canScrollVertically()
+                ? getVerticalHelper(layoutManager).getDecoratedStart(targetView)
+                : 0;
+
+        return out;
+    }
+
+    /**
+     * Finds the view to snap to. The view to snap to is the child of the LayoutManager that is
+     * closest to the start of the RecyclerView. The "start" depends on if the LayoutManager
+     * is scrolling horizontally or vertically. If it is horizontally scrolling, then the
+     * start is the view on the left (right if RTL). Otherwise, it is the top-most view.
+     *
+     * @param layoutManager The current {@link RecyclerView.LayoutManager} for the attached
+     *                      RecyclerView.
+     * @return The View closest to the start of the RecyclerView.
+     */
+    @Override
+    @Nullable
+    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
+        int childCount = layoutManager.getChildCount();
+        if (childCount == 0) {
+            return null;
+        }
+
+        // If there's only one child, then that will be the snap target.
+        if (childCount == 1) {
+            return layoutManager.getChildAt(0);
+        }
+
+        OrientationHelper orientationHelper = layoutManager.canScrollVertically()
+                ? getVerticalHelper(layoutManager)
+                : getHorizontalHelper(layoutManager);
+
+        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+
+        // Check if the last child visible is the last item in the list.
+        boolean lastItemVisible =
+                layoutManager.getPosition(lastVisibleChild) == layoutManager.getItemCount() - 1;
+
+        // If it is, then check how much of that view is visible.
+        float lastItemPercentageVisible = lastItemVisible
+                ? getPercentageVisible(lastVisibleChild, orientationHelper) : 0;
+
+        View closestChild = null;
+        int closestDistanceToStart = Integer.MAX_VALUE;
+        float closestPercentageVisible = 0.f;
+
+        for (int i = 0; i < childCount; i++) {
+            View child = layoutManager.getChildAt(i);
+            int startOffset = orientationHelper.getDecoratedStart(child);
+
+            if (Math.abs(startOffset) < closestDistanceToStart) {
+                float percentageVisible = getPercentageVisible(child, orientationHelper);
+
+                // Only snap to the child that is closest to the top and is more than
+                // half-way visible.
+                if (percentageVisible > VIEW_VISIBLE_THRESHOLD
+                        && percentageVisible > closestPercentageVisible) {
+                    closestDistanceToStart = startOffset;
+                    closestChild = child;
+                    closestPercentageVisible = percentageVisible;
+                }
+            }
+        }
+
+        // Snap to the last child in the list if it's the last item in the list, and it's more
+        // visible than the closest item to the top of the list.
+        return (lastItemVisible && lastItemPercentageVisible > closestPercentageVisible)
+                ? lastVisibleChild
+                : closestChild;
+    }
+
+    /**
+     * Returns the percentage of the given view that is visible, relative to its containing
+     * RecyclerView.
+     *
+     * @param view The View to get the percentage visible of.
+     * @param helper An {@link OrientationHelper} to aid with calculation.
+     * @return A float indicating the percentage of the given view that is visible.
+     */
+    private float getPercentageVisible(View view, OrientationHelper helper) {
+        int start = 0;
+        int end = helper.getEnd();
+
+        int viewStart = helper.getDecoratedStart(view);
+        int viewEnd = helper.getDecoratedEnd(view);
+
+        if (viewStart >= start && viewEnd <= end) {
+            // The view is within the bounds of the RecyclerView, so it's fully visible.
+            return 1.f;
+        } else if (viewStart <= start && viewEnd >= end) {
+            // The view is larger than the height of the RecyclerView.
+            int viewHeight = helper.getDecoratedMeasurement(view);
+            return 1.f - ((float) (Math.abs(viewStart) + Math.abs(viewEnd)) / viewHeight);
+        } else if (viewStart < start) {
+            // The view is above the start of the RecyclerView, so subtract the start offset
+            // from the total height.
+            return 1.f - ((float) Math.abs(viewStart) / helper.getDecoratedMeasurement(view));
+        } else {
+            // The view is below the end of the RecyclerView, so subtract the end offset from the
+            // total height.
+            return 1.f - ((float) Math.abs(viewEnd) / helper.getDecoratedMeasurement(view));
+        }
+    }
+
+    @Override
+    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
+        super.attachToRecyclerView(recyclerView);
+        mRecyclerView = recyclerView;
+    }
+
+    /**
+     * Calculate the estimated scroll distance in each direction given velocities on both axes.
+     * This method will clamp the maximum scroll distance so that a single fling will never scroll
+     * more than one page.
+     *
+     * @param velocityX Fling velocity on the horizontal axis.
+     * @param velocityY Fling velocity on the vertical axis.
+     * @return An array holding the calculated distances in x and y directions respectively.
+     */
+    @Override
+    public int[] calculateScrollDistance(int velocityX, int velocityY) {
+        int[] outDist = super.calculateScrollDistance(velocityX, velocityY);
+
+        if (mRecyclerView == null) {
+            return outDist;
+        }
+
+        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return outDist;
+        }
+
+        int lastChildPosition = isAtEnd(layoutManager) ? 0 : layoutManager.getChildCount() - 1;
+
+        // The max and min distance is the total height of the RecyclerView minus the height of
+        // the last child. This ensures that each scroll will never scroll more than a single
+        // page on the RecyclerView. That is, the max scroll will make the last child the
+        // first child and vice versa when scrolling the opposite way.
+        int maxDistance = layoutManager.getHeight() - layoutManager.getDecoratedMeasuredHeight(
+                layoutManager.getChildAt(lastChildPosition));
+        int minDistance = -maxDistance;
+
+        outDist[0] = clamp(outDist[0], minDistance, maxDistance);
+        outDist[1] = clamp(outDist[1], minDistance, maxDistance);
+
+        return outDist;
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the first item. */
+    public boolean isAtStart(RecyclerView.LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        View firstChild = layoutManager.getChildAt(0);
+        OrientationHelper orientationHelper = layoutManager.canScrollVertically()
+                ? getVerticalHelper(layoutManager)
+                : getHorizontalHelper(layoutManager);
+
+        // Check that the first child is completely visible and is the first item in the list.
+        return orientationHelper.getDecoratedStart(firstChild) >= 0
+                && layoutManager.getPosition(firstChild) == 0;
+    }
+
+    /** Returns {@code true} if the RecyclerView is completely displaying the last item. */
+    public boolean isAtEnd(RecyclerView.LayoutManager layoutManager) {
+        if (layoutManager == null || layoutManager.getChildCount() == 0) {
+            return true;
+        }
+
+        int childCount = layoutManager.getChildCount();
+        View lastVisibleChild = layoutManager.getChildAt(childCount - 1);
+
+        // The list has reached the bottom if the last child that is visible is the last item
+        // in the list and it's fully shown.
+        return layoutManager.getPosition(lastVisibleChild) == (layoutManager.getItemCount() - 1)
+                && layoutManager.getDecoratedBottom(lastVisibleChild) <= layoutManager.getHeight();
+    }
+
+    @NonNull
+    private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {
+        if (mVerticalHelper == null || mVerticalHelper.getLayoutManager() != layoutManager) {
+            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
+        }
+        return mVerticalHelper;
+    }
+
+    @NonNull
+    private OrientationHelper getHorizontalHelper(
+            @NonNull RecyclerView.LayoutManager layoutManager) {
+        if (mHorizontalHelper == null || mHorizontalHelper.getLayoutManager() != layoutManager) {
+            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
+        }
+        return mHorizontalHelper;
+    }
+
+    /**
+     * Ensures that the given value falls between the range given by the min and max values. This
+     * method does not check that the min value is greater than or equal to the max value. If the
+     * parameters are not well-formed, this method's behavior is undefined.
+     *
+     * @param value The value to clamp.
+     * @param min The minimum value the given value can be.
+     * @param max The maximum value the given value can be.
+     * @return A number that falls between {@code min} or {@code max} or one of those values if the
+     * given value is less than or greater than {@code min} and {@code max} respectively.
+     */
+    private static int clamp(int value, int min, int max) {
+        return Math.max(min, Math.min(max, value));
+    }
+}
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/exifinterface/tests/NO_DOCS b/car/tests/NO_DOCS
similarity index 100%
copy from exifinterface/tests/NO_DOCS
copy to car/tests/NO_DOCS
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..bdf3e29
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/ListItemTest.java
@@ -0,0 +1,577 @@
+/*
+ * 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.support.v7.widget.LinearLayoutManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+
+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.getRecyclerView().getLayoutManager().getChildAt(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, v -> { /* Do nothing. */ })
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withActions("text", true, v -> { /* Do nothing. */ },
+                                 "text", true, v -> { /* Do nothing. */ })
+                        .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, v -> { /* Do nothing. */ })
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withActions("text", false, v -> { /* Do nothing. */ },
+                                "text", false, v -> { /* Do nothing. */ })
+                        .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);
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+        for (int i = 0; i < items.size(); i++) {
+            assertThat((double) layoutManager.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);
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+        for (int i = 0; i < items.size(); i++) {
+            assertThat(layoutManager.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 testSupplementalIconWithoutClickListenerIsNotClickable() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true)
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertFalse(viewHolder.getSupplementalIcon().isClickable());
+    }
+
+    @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())));
+    }
+
+    @Test
+    public void testNoCarriedOverLayoutParamsForTextView() throws Throwable {
+        ListItem singleLine = new ListItem.Builder(mActivity).withTitle("t").build();
+        setupPagedListView(Arrays.asList(singleLine));
+
+        // Manually rebind the view holder of a single line item to a double line item.
+        ListItem doubleLine = new ListItem.Builder(mActivity).withTitle("t").withBody("b").build();
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        mActivityRule.runOnUiThread(() -> doubleLine.bind(viewHolder));
+
+        RelativeLayout.LayoutParams titleLayoutParams =
+                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
+        RelativeLayout.LayoutParams bodyLayoutParams =
+                (RelativeLayout.LayoutParams) viewHolder.getTitle().getLayoutParams();
+        assertThat(titleLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
+        assertThat(bodyLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL), is(equalTo(0)));
+    }
+
+    @Test
+    public void testNoCarriedOverLayoutParamsForPrimaryIcon() throws Throwable {
+        ListItem smallIcon = new ListItem.Builder(mActivity)
+                .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                .withBody("body")  // Small icon of items with body text should use top margin.
+                .build();
+        setupPagedListView(Arrays.asList(smallIcon));
+
+        // Manually rebind the view holder.
+        ListItem largeIcon = new ListItem.Builder(mActivity)
+                .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                .build();
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        mActivityRule.runOnUiThread(() -> largeIcon.bind(viewHolder));
+
+        RelativeLayout.LayoutParams iconLayoutParams =
+                (RelativeLayout.LayoutParams) viewHolder.getPrimaryIcon().getLayoutParams();
+        assertThat(iconLayoutParams.getRule(RelativeLayout.CENTER_VERTICAL),
+                is(equalTo(RelativeLayout.TRUE)));
+        assertThat(iconLayoutParams.topMargin, is(equalTo(0)));
+    }
+
+    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..a5405ac
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.LinearLayoutManager;
+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;
+        }
+
+        LinearLayoutManager layoutManager1 =
+                (LinearLayoutManager) mPagedListView1.getRecyclerView().getLayoutManager();
+        LinearLayoutManager layoutManager2 =
+                (LinearLayoutManager) mPagedListView2.getRecyclerView().getLayoutManager();
+
+        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 = 2;
+        clickPageDownButton(onPagedListView1(), numOfClicks);
+        int topPositionOfPagedListView1 =
+                layoutManager1.findFirstVisibleItemPosition();
+
+        numOfClicks = 3;
+        clickPageDownButton(onPagedListView2(), numOfClicks);
+        int topPositionOfPagedListView2 =
+                layoutManager2.findFirstVisibleItemPosition();
+
+        // 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,
+                layoutManager1.findFirstVisibleItemPosition());
+        assertEquals(topPositionOfPagedListView2,
+                layoutManager2.findFirstVisibleItemPosition());
+    }
+
+    /** 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..67d97e3
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewTest.java
@@ -0,0 +1,463 @@
+/*
+ * 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.doesNotExist;
+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.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.content.pm.PackageManager;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.test.InstrumentationRegistry;
+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.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.LinearLayoutManager;
+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)
+@MediumTest
+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);
+        }
+    }
+
+    /** Returns {@code true} if the testing device has the automotive feature flag. */
+    private boolean isAutoDevice() {
+        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    /** Sets up {@link #mPagedListView} with the given number of items. */
+    private void setUpPagedListView(int itemCount) {
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mPagedListView.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+                mPagedListView.setAdapter(
+                        new TestAdapter(itemCount, mPagedListView.getMeasuredHeight()));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    @Test
+    public void testScrollBarIsInvisibleIfItemsDoNotFillOnePage() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        setUpPagedListView(1 /* itemCount */);
+        onView(withId(R.id.paged_scroll_view)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testPageUpButtonDisabledAtTop() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        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 down, should enable the up bottom.
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_up)).check(matches(isEnabled()));
+
+        // Move back up; this should disable the up bottom again.
+        onView(withId(R.id.page_up)).perform(click())
+                .check(matches(not(isEnabled())));
+    }
+
+    @Test
+    public void testItemSnappedToTopOfListOnScroll() throws InterruptedException {
+        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();
+    }
+
+    @Test
+    public void testLastItemSnappedWhenBottomReached() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // 2.5 so last page is not full
+        setUpPagedListView((int) (ITEMS_PER_PAGE * 2.5 /* itemCount */));
+
+        // Go down 2 pages so the bottom is reached.
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_down)).perform(click()).check(matches(not(isEnabled())));
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+
+        // Check that the last item is completely visible.
+        assertEquals(layoutManager.findLastCompletelyVisibleItemPosition(),
+                layoutManager.getItemCount() - 1);
+    }
+
+    @Test
+    public void testSwipeDownKeepsItemSnappedToTopOfList() {
+        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 testSwipeUpKeepsItemSnappedToTopOfList() {
+        setUpPagedListView(ITEMS_PER_PAGE * 2 /* itemCount */);
+
+        // Swipe up (going down).
+        onView(withId(R.id.recycler_view))
+                .perform(actionOnItemAtPosition(ITEMS_PER_PAGE, swipeUp()));
+
+        verifyItemSnappedToListTop();
+    }
+
+    @Test
+    public void testPageUpAndDownMoveSameDistance() {
+        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());
+
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+
+        int topPosition = layoutManager.findFirstVisibleItemPosition();
+
+        for (int i = 0; i < 3; i++) {
+            onView(withId(R.id.page_down)).perform(click());
+            onView(withId(R.id.page_up)).perform(click());
+        }
+
+        assertThat(layoutManager.findFirstVisibleItemPosition(), is(equalTo(topPosition)));
+    }
+
+    @Test
+    public void setItemSpacing() throws Throwable {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        final int itemCount = 3;
+        setUpPagedListView(itemCount /* itemCount */);
+        RecyclerView.LayoutManager layoutManager =
+                mPagedListView.getRecyclerView().getLayoutManager();
+
+        // Initial spacing is 0.
+        final View[] views = new View[itemCount];
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < layoutManager.getChildCount(); i++) {
+                views[i] = layoutManager.getChildAt(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 < layoutManager.getChildCount(); i++) {
+                views[i] = layoutManager.getChildAt(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 < layoutManager.getChildCount(); i++) {
+                views[i] = layoutManager.getChildAt(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 {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // 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())));
+    }
+
+    @Test
+    public void testSettingAndResettingScrollbarColor() {
+        setUpPagedListView(0);
+
+        final int color = R.color.car_teal_700;
+
+        // Setting non-zero res ID changes color.
+        mPagedListView.setScrollbarColor(color);
+        assertThat(((ColorDrawable)
+                        mPagedListView.mScrollBarView.mScrollThumb.getBackground()).getColor(),
+                is(equalTo(InstrumentationRegistry.getContext().getColor(color))));
+
+        // Resets to default color.
+        mPagedListView.resetScrollbarColor();
+        assertThat(((ColorDrawable)
+                        mPagedListView.mScrollBarView.mScrollThumb.getBackground()).getColor(),
+                is(equalTo(InstrumentationRegistry.getContext().getColor(
+                        R.color.car_scrollbar_thumb))));
+    }
+
+    @Test
+    public void testSettingScrollbarColorIgnoresDayNightStyle() {
+        setUpPagedListView(0);
+
+        final int color = R.color.car_teal_700;
+        mPagedListView.setScrollbarColor(color);
+
+        for (int style : new int[] {DayNightStyle.AUTO, DayNightStyle.AUTO_INVERSE,
+                DayNightStyle.FORCE_NIGHT, DayNightStyle.FORCE_DAY}) {
+            mPagedListView.setDayNightStyle(style);
+
+            assertThat(((ColorDrawable)
+                            mPagedListView.mScrollBarView.mScrollThumb.getBackground()).getColor(),
+                    is(equalTo(InstrumentationRegistry.getContext().getColor(color))));
+        }
+    }
+
+    private static String itemText(int index) {
+        return "Data " + index;
+    }
+
+    /**
+     * Checks that the first item in the list is completely shown and no part of a previous item
+     * is shown.
+     */
+    private void verifyItemSnappedToListTop() {
+        LinearLayoutManager layoutManager =
+                (LinearLayoutManager) mPagedListView.getRecyclerView().getLayoutManager();
+        int firstVisiblePosition = layoutManager.findFirstCompletelyVisibleItemPosition();
+        if (firstVisiblePosition > 1) {
+            int lastInPreviousPagePosition = firstVisiblePosition - 1;
+            onView(withText(itemText(lastInPreviousPagePosition)))
+                    .check(doesNotExist());
+        }
+    }
+
+    /** A base adapter that will handle inflating the test view and binding data to it. */
+    private class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+        private List<String> mData;
+        private int mParentHeight;
+
+        TestAdapter(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));
+        }
+
+        @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);
+        }
+    }
+
+    /**
+     * An {@link IdlingResource} that will prevent assertions from running while the
+     * {@link #mPagedListView} is scrolling.
+     */
+    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/Android.mk b/compat/Android.mk
index e16bba6..720a1eb 100644
--- a/compat/Android.mk
+++ b/compat/Android.mk
@@ -30,7 +30,7 @@
     $(call all-java-files-under,src/main/java) \
     $(call all-Iaidl-files-under,src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
 LOCAL_STATIC_JAVA_LIBRARIES := \
     apptoolkit-arch-core-common \
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 5bbbb6f..8f44285 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -10,11 +10,13 @@
     api(project(":support-annotations"))
     api(ARCH_LIFECYCLE_RUNTIME, libs.exclude_annotations_transitive)
 
-    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/hardware/fingerprint/FingerprintManagerCompat.java b/compat/src/main/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
index 68f9476..6747d11 100644
--- a/compat/src/main/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
+++ b/compat/src/main/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
@@ -16,7 +16,6 @@
 
 package android.support.v4.hardware.fingerprint;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
@@ -58,7 +57,6 @@
      *
      * @return true if at least one fingerprint is enrolled, false otherwise
      */
-    @TargetApi(23)
     @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
     public boolean hasEnrolledFingerprints() {
         if (Build.VERSION.SDK_INT >= 23) {
@@ -74,7 +72,6 @@
      *
      * @return true if hardware is present and functional, false otherwise.
      */
-    @TargetApi(23)
     @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
     public boolean isHardwareDetected() {
         if (Build.VERSION.SDK_INT >= 23) {
@@ -99,7 +96,6 @@
      * @param callback an object to receive authentication events
      * @param handler an optional handler for events
      */
-    @TargetApi(23)
     @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT)
     public void authenticate(@Nullable CryptoObject crypto, int flags,
             @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback,
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/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..41da709 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -31,9 +31,11 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-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;
@@ -180,7 +182,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 24)
-    @TargetApi(24)
     @Test
     public void testFrameworkNotificationActionBuilder_setAllowGeneratedRepliesTrue()
             throws Throwable {
@@ -214,7 +215,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testNotificationWearableExtenderAction_setAllowGeneratedRepliesTrue()
             throws Throwable {
@@ -228,7 +228,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testNotificationWearableExtenderAction_setAllowGeneratedRepliesFalse()
             throws Throwable {
@@ -424,6 +423,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/app/RemoteInputTest.java b/compat/tests/java/android/support/v4/app/RemoteInputTest.java
index 55d6cca..4c3d81b 100644
--- a/compat/tests/java/android/support/v4/app/RemoteInputTest.java
+++ b/compat/tests/java/android/support/v4/app/RemoteInputTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.annotation.TargetApi;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
@@ -96,7 +95,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testRemoteInputBuilder_addAndGetDataResultsFromIntent() throws Throwable {
         Uri uri = Uri.parse("Some Uri");
@@ -110,7 +108,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testRemoteInputBuilder_addAndGetTextResultsFromIntent() throws Throwable {
         CharSequence charSequence = "value doesn't matter";
@@ -126,7 +123,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testRemoteInputBuilder_addAndGetDataAndTextResultsFromIntentDataFirst()
             throws Throwable {
@@ -153,7 +149,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 17)
-    @TargetApi(17)
     @Test
     public void testRemoteInputBuilder_addAndGetDataAndTextResultsFromIntentTextFirst()
             throws Throwable {
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..36cd741 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;
@@ -32,7 +33,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.TargetApi;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -45,8 +45,8 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.graphics.Bitmap;
-import android.os.Build;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.BaseInstrumentationTestCase;
@@ -85,12 +85,8 @@
 
     @Test
     @SmallTest
-    @TargetApi(26)
+    @SdkSuppress(minSdkVersion = 26)
     public void testIsRequestPinShortcutSupported_v26() throws Throwable {
-        if (!(Build.VERSION.SDK_INT >= 26)) {
-            return;
-        }
-
         ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
         doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
         when(mockShortcutManager.isRequestPinShortcutSupported()).thenReturn(true, false, true);
@@ -103,16 +99,12 @@
 
     @Test
     @SmallTest
-    @TargetApi(26)
+    @SdkSuppress(minSdkVersion = 26)
     public void testRequestPinShortcut_v26()  throws Throwable {
-        if (!(Build.VERSION.SDK_INT >= 26)) {
-            return;
-        }
-
         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);
@@ -123,12 +115,8 @@
 
     @Test
     @SmallTest
-    @TargetApi(26)
+    @SdkSuppress(minSdkVersion = 26)
     public void testCreateShortcutResultIntent_v26()  throws Throwable {
-        if (!(Build.VERSION.SDK_INT >= 26)) {
-            return;
-        }
-
         ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
         doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
 
@@ -146,10 +134,8 @@
 
     @SmallTest
     @Test
+    @SdkSuppress(maxSdkVersion = 25)
     public void testIsRequestPinShortcutSupported_v4() throws Throwable {
-        if (Build.VERSION.SDK_INT >= 26) {
-            return;
-        }
         setMockPm(mockResolveInfo(null));
         assertTrue(ShortcutManagerCompat.isRequestPinShortcutSupported(mContext));
 
@@ -173,11 +159,8 @@
 
     @LargeTest
     @Test
+    @SdkSuppress(maxSdkVersion = 25)
     public void testRequestPinShortcut_v4_noCallback()  throws Throwable {
-        if (Build.VERSION.SDK_INT >= 26) {
-            return;
-        }
-
         setMockPm(mockResolveInfo(null));
 
         BlockingBroadcastReceiver receiver =
@@ -188,11 +171,8 @@
 
     @LargeTest
     @Test
+    @SdkSuppress(maxSdkVersion = 25)
     public void testRequestPinShortcut_v4_withCallback()  throws Throwable {
-        if (Build.VERSION.SDK_INT >= 26) {
-            return;
-        }
-
         setMockPm(mockResolveInfo(null));
 
         BlockingBroadcastReceiver receiver =
@@ -209,11 +189,8 @@
 
     @SmallTest
     @Test
+    @SdkSuppress(maxSdkVersion = 25)
     public void testCreateShortcutResultIntent_v4() throws Throwable {
-        if (Build.VERSION.SDK_INT >= 26) {
-            return;
-        }
-
         verifyLegacyIntent(ShortcutManagerCompat.createShortcutResultIntent(mContext, mInfoCompat));
     }
 
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..c84b19a 100644
--- a/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java
+++ b/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java
@@ -17,9 +17,11 @@
 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;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -34,6 +36,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 +48,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 +56,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,13 +86,47 @@
         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() {
         Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
         bitmap.eraseColor(Color.RED);
@@ -90,7 +141,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());
@@ -98,7 +149,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
-    @TargetApi(Build.VERSION_CODES.O)
     public void testCreateWithAdaptiveBitmap() {
         Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
         bitmap.eraseColor(Color.GREEN);
@@ -115,7 +165,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
-    @TargetApi(Build.VERSION_CODES.M)
     public void testCreateWithData() {
         Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
         bitmap.eraseColor(Color.YELLOW);
@@ -138,7 +187,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
-    @TargetApi(Build.VERSION_CODES.M)
     public void testCreateWithResource() {
         Context context = InstrumentationRegistry.getContext();
         Drawable original = context.getDrawable(R.drawable.test_drawable_red);
diff --git a/compat/tests/java/android/support/v4/os/LocaleListCompatTest.java b/compat/tests/java/android/support/v4/os/LocaleListCompatTest.java
index a97a033..6714780 100644
--- a/compat/tests/java/android/support/v4/os/LocaleListCompatTest.java
+++ b/compat/tests/java/android/support/v4/os/LocaleListCompatTest.java
@@ -26,7 +26,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.annotation.TargetApi;
 import android.os.Build;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
@@ -262,7 +261,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 24)
-    @TargetApi(24)
     @Test
     public void testGetFirstMatch_oneChineseAsset() {
         String[] oneChineseAsset = {"zh-CN"};  // Assumed to mean zh-Hans-CN
@@ -300,7 +298,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 24)
-    @TargetApi(24)
     @Test
     public void testGetFirstMatch_serbianCyrillic() {
         String[] oneSerbianAsset = {"sr"};  // Assumed to mean sr-Cyrl-RS
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/Android.mk b/content/Android.mk
index 32caf53..1eca4f5 100644
--- a/content/Android.mk
+++ b/content/Android.mk
@@ -20,9 +20,10 @@
 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-compat \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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 e3d59d9..cff7db7 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)
 }
 
 supportLibrary {
diff --git a/core-ui/Android.mk b/core-ui/Android.mk
index 184d7be..1473381 100644
--- a/core-ui/Android.mk
+++ b/core-ui/Android.mk
@@ -28,10 +28,13 @@
 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_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
-    android-support-annotations
+    android-support-core-utils
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+LOCAL_EXPORT_PROGUARD_FLAG_FILES := proguard-rules.pro
 include $(BUILD_STATIC_JAVA_LIBRARY)
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 70bbb8f..4ff27cb 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -9,15 +9,26 @@
 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 {
+    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..03cce02
--- /dev/null
+++ b/core-ui/src/main/java/android/support/design/widget/CoordinatorLayout.java
@@ -0,0 +1,3250 @@
+/*
+ * 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();
+        }
+        mBehaviorTouchView = null;
+        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/DrawerLayout.java b/core-ui/src/main/java/android/support/v4/widget/DrawerLayout.java
index a73e1f1..aa2077d 100644
--- a/core-ui/src/main/java/android/support/v4/widget/DrawerLayout.java
+++ b/core-ui/src/main/java/android/support/v4/widget/DrawerLayout.java
@@ -44,7 +44,6 @@
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewGroupCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
 import android.util.AttributeSet;
@@ -331,7 +330,7 @@
                 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
-        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
+        setMotionEventSplittingEnabled(false);
         if (ViewCompat.getFitsSystemWindows(this)) {
             if (Build.VERSION.SDK_INT >= 21) {
                 setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
diff --git a/core-ui/src/main/java/android/support/v4/widget/NestedScrollView.java b/core-ui/src/main/java/android/support/v4/widget/NestedScrollView.java
index 73ff084..6fe1928 100644
--- a/core-ui/src/main/java/android/support/v4/widget/NestedScrollView.java
+++ b/core-ui/src/main/java/android/support/v4/widget/NestedScrollView.java
@@ -23,6 +23,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1820,10 +1821,20 @@
             final int scrollY = getScrollY();
             if (!mEdgeGlowTop.isFinished()) {
                 final int restoreCount = canvas.save();
-                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
-                canvas.translate(getPaddingLeft(), Math.min(0, scrollY));
-                mEdgeGlowTop.setSize(width, getHeight());
+                int width = getWidth();
+                int height = getHeight();
+                int xTranslation = 0;
+                int yTranslation = Math.min(0, scrollY);
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) {
+                    width -= getPaddingLeft() + getPaddingRight();
+                    xTranslation += getPaddingLeft();
+                }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) {
+                    height -= getPaddingTop() + getPaddingBottom();
+                    yTranslation += getPaddingTop();
+                }
+                canvas.translate(xTranslation, yTranslation);
+                mEdgeGlowTop.setSize(width, height);
                 if (mEdgeGlowTop.draw(canvas)) {
                     ViewCompat.postInvalidateOnAnimation(this);
                 }
@@ -1831,11 +1842,19 @@
             }
             if (!mEdgeGlowBottom.isFinished()) {
                 final int restoreCount = canvas.save();
-                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
-                final int height = getHeight();
-
-                canvas.translate(-width + getPaddingLeft(),
-                        Math.max(getScrollRange(), scrollY) + height);
+                int width = getWidth();
+                int height = getHeight();
+                int xTranslation = 0;
+                int yTranslation = Math.max(getScrollRange(), scrollY) + height;
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) {
+                    width -= getPaddingLeft() + getPaddingRight();
+                    xTranslation += getPaddingLeft();
+                }
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) {
+                    height -= getPaddingTop() + getPaddingBottom();
+                    yTranslation -= getPaddingBottom();
+                }
+                canvas.translate(xTranslation - width, yTranslation);
                 canvas.rotate(180, width, 0);
                 mEdgeGlowBottom.setSize(width, height);
                 if (mEdgeGlowBottom.draw(canvas)) {
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..9b4c586
--- /dev/null
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
@@ -0,0 +1,781 @@
+/*
+ * 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.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)
+    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..751091b 100644
--- a/core-utils/Android.mk
+++ b/core-utils/Android.mk
@@ -27,11 +27,14 @@
 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 \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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 ad3b9df..75555c0 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -10,12 +10,22 @@
     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
 }
 
+android {
+    sourceSets {
+        main.java.srcDirs = [
+                'kitkat',
+                'api21',
+                'java'
+        ]
+    }
+}
+
 supportLibrary {
     name = "Android Support Library core utils"
     publish = true
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/Android.mk b/customtabs/Android.mk
index f9195e8..30132a1 100644
--- a/customtabs/Android.mk
+++ b/customtabs/Android.mk
@@ -31,8 +31,9 @@
     $(call all-java-files-under,src/main/java) \
     $(call all-Iaidl-files-under,src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-compat
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index c13124f..cf1c775 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..f856de6 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -27,6 +27,7 @@
 # in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
 LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE := android-support-design
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := \
@@ -34,14 +35,20 @@
     $(call all-java-files-under,lollipop) \
     $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-transition \
     android-support-v7-appcompat \
     android-support-v7-recyclerview \
-    android-support-v4 \
-    android-support-annotations
+    android-support-compat \
+    android-support-media-compat \
+    android-support-core-utils \
+    android-support-core-ui \
+    android-support-fragment
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := \
     --no-version-vectors \
     --add-javadoc-annotation doconly
+LOCAL_EXPORT_PROGUARD_FLAG_FILES := proguard-rules.pro
 include $(BUILD_STATIC_JAVA_LIBRARY)
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 e26a951..baa94ea 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 {
@@ -42,8 +39,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/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index a1ea451..5ac3580 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -61,7 +61,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
-import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Parcelable;
@@ -70,7 +69,6 @@
 import android.support.design.test.R;
 import android.support.design.testutils.TestDrawable;
 import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
@@ -406,8 +404,6 @@
         verifyHeaders(R.id.header2, R.id.header3, R.id.header3);
     }
 
-    @SdkSuppress(minSdkVersion = 11)
-    @TargetApi(11)
     @Test
     public void testHeaderState() {
         // Open our drawer
@@ -446,8 +442,6 @@
                 .check(matches(isChecked()));
     }
 
-    @SdkSuppress(minSdkVersion = 11)
-    @TargetApi(11)
     @Test
     public void testActionViewState() {
         // Open our drawer
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/Android.mk b/dynamic-animation/Android.mk
index 11aa484..98e8f45 100644
--- a/dynamic-animation/Android.mk
+++ b/dynamic-animation/Android.mk
@@ -20,9 +20,10 @@
 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-compat \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index f820bb9..10e49e0 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/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java b/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
index 9f63bd1..6c39b23 100644
--- a/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
+++ b/dynamic-animation/src/main/java/android/support/animation/AnimationHandler.java
@@ -16,11 +16,11 @@
 
 package android.support.animation;
 
-import android.annotation.TargetApi;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.support.annotation.RequiresApi;
 import android.support.v4.util.SimpleArrayMap;
 import android.view.Choreographer;
 
@@ -191,7 +191,7 @@
     /**
      * Default provider of timing pulse that uses Choreographer for frame callbacks.
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
     private static class FrameCallbackProvider16 extends AnimationFrameCallbackProvider {
 
         private final Choreographer mChoreographer = Choreographer.getInstance();
diff --git a/dynamic-animation/src/main/java/android/support/animation/FloatPropertyCompat.java b/dynamic-animation/src/main/java/android/support/animation/FloatPropertyCompat.java
index cde340c..ec8d0ca 100644
--- a/dynamic-animation/src/main/java/android/support/animation/FloatPropertyCompat.java
+++ b/dynamic-animation/src/main/java/android/support/animation/FloatPropertyCompat.java
@@ -16,7 +16,7 @@
 
 package android.support.animation;
 
-import android.annotation.TargetApi;
+import android.support.annotation.RequiresApi;
 import android.util.FloatProperty;
 
 /**
@@ -51,7 +51,7 @@
      * @param <T> the class on which the Property is declared
      * @return a new {@link FloatPropertyCompat} wrapper for the given {@link FloatProperty} object
      */
-    @TargetApi(24)
+    @RequiresApi(24)
     public static <T> FloatPropertyCompat<T> createFloatPropertyCompat(
             final FloatProperty<T> property) {
         return new FloatPropertyCompat<T>(property.getName()) {
diff --git a/emoji/appcompat/Android.mk b/emoji/appcompat/Android.mk
index f04d158..0ef8cb7 100644
--- a/emoji/appcompat/Android.mk
+++ b/emoji/appcompat/Android.mk
@@ -30,8 +30,9 @@
 LOCAL_MODULE := android-support-emoji-appcompat
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-v7-appcompat \
     android-support-emoji \
     android-support-compat
diff --git a/emoji/bundled/Android.mk b/emoji/bundled/Android.mk
index 29f0d58..c5c1ac9 100644
--- a/emoji/bundled/Android.mk
+++ b/emoji/bundled/Android.mk
@@ -29,8 +29,9 @@
 LOCAL_MODULE := android-support-emoji-bundled
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-emoji \
     android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
diff --git a/emoji/core/Android.mk b/emoji/core/Android.mk
index 8b7a032..89426f0 100644
--- a/emoji/core/Android.mk
+++ b/emoji/core/Android.mk
@@ -31,8 +31,9 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
 LOCAL_STATIC_JAVA_LIBRARIES := \
     noto-emoji-compat-java
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
diff --git a/emoji/core/api/current.txt b/emoji/core/api/current.txt
index d5ce666..40bd7d8 100644
--- a/emoji/core/api/current.txt
+++ b/emoji/core/api/current.txt
@@ -32,6 +32,8 @@
     method public android.support.text.emoji.EmojiCompat.Config setEmojiSpanIndicatorColor(int);
     method public android.support.text.emoji.EmojiCompat.Config setEmojiSpanIndicatorEnabled(boolean);
     method public android.support.text.emoji.EmojiCompat.Config setReplaceAll(boolean);
+    method public android.support.text.emoji.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean);
+    method public android.support.text.emoji.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean, java.util.List<java.lang.Integer>);
     method public android.support.text.emoji.EmojiCompat.Config unregisterInitCallback(android.support.text.emoji.EmojiCompat.InitCallback);
   }
 
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index d3ca905..8d470f0 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')
@@ -73,3 +75,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/EmojiCompat.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
index 5436aa2..413a9dd 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiCompat.java
@@ -184,6 +184,16 @@
     private final boolean mReplaceAll;
 
     /**
+     * @see Config#setUseEmojiAsDefaultStyle(boolean)
+     */
+    private final boolean mUseEmojiAsDefaultStyle;
+
+    /**
+     * @see Config#setUseEmojiAsDefaultStyle(boolean, List)
+     */
+    private final int[] mEmojiAsDefaultStyleExceptions;
+
+    /**
      * @see Config#setEmojiSpanIndicatorEnabled(boolean)
      */
     private final boolean mEmojiSpanIndicatorEnabled;
@@ -201,6 +211,8 @@
     private EmojiCompat(@NonNull final Config config) {
         mInitLock = new ReentrantReadWriteLock();
         mReplaceAll = config.mReplaceAll;
+        mUseEmojiAsDefaultStyle = config.mUseEmojiAsDefaultStyle;
+        mEmojiAsDefaultStyleExceptions = config.mEmojiAsDefaultStyleExceptions;
         mEmojiSpanIndicatorEnabled = config.mEmojiSpanIndicatorEnabled;
         mEmojiSpanIndicatorColor = config.mEmojiSpanIndicatorColor;
         mMetadataLoader = config.mMetadataLoader;
@@ -787,6 +799,8 @@
     public abstract static class Config {
         private final MetadataRepoLoader mMetadataLoader;
         private boolean mReplaceAll;
+        private boolean mUseEmojiAsDefaultStyle;
+        private int[] mEmojiAsDefaultStyleExceptions;
         private Set<InitCallback> mInitCallbacks;
         private boolean mEmojiSpanIndicatorEnabled;
         private int mEmojiSpanIndicatorColor = Color.GREEN;
@@ -849,6 +863,56 @@
         }
 
         /**
+         * Determines whether EmojiCompat should use the emoji presentation style for emojis
+         * that have text style as default. By default, the text style would be used, unless these
+         * are followed by the U+FE0F variation selector.
+         * Details about emoji presentation and text presentation styles can be found here:
+         * http://unicode.org/reports/tr51/#Presentation_Style
+         * If useEmojiAsDefaultStyle is true, the emoji presentation style will be used for all
+         * emojis, including potentially unexpected ones (such as digits or other keycap emojis). If
+         * this is not the expected behaviour, method
+         * {@link #setUseEmojiAsDefaultStyle(boolean, List)} can be used to specify the
+         * exception emojis that should be still presented as text style.
+         *
+         * @param useEmojiAsDefaultStyle whether to use the emoji style presentation for all emojis
+         *                               that would be presented as text style by default
+         */
+        public Config setUseEmojiAsDefaultStyle(final boolean useEmojiAsDefaultStyle) {
+            return setUseEmojiAsDefaultStyle(useEmojiAsDefaultStyle, null);
+        }
+
+        /**
+         * @see #setUseEmojiAsDefaultStyle(boolean)
+         *
+         * @param emojiAsDefaultStyleExceptions Contains the exception emojis which will be still
+         *                                      presented as text style even if the
+         *                                      useEmojiAsDefaultStyle flag is set to {@code true}.
+         *                                      This list will be ignored if useEmojiAsDefaultStyle
+         *                                      is {@code false}. Note that emojis with default
+         *                                      emoji style presentation will remain emoji style
+         *                                      regardless the value of useEmojiAsDefaultStyle or
+         *                                      whether they are included in the exceptions list or
+         *                                      not. When no exception is wanted, the method
+         *                                      {@link #setUseEmojiAsDefaultStyle(boolean)} should
+         *                                      be used instead.
+         */
+        public Config setUseEmojiAsDefaultStyle(final boolean useEmojiAsDefaultStyle,
+                @Nullable final List<Integer> emojiAsDefaultStyleExceptions) {
+            mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+            if (mUseEmojiAsDefaultStyle && emojiAsDefaultStyleExceptions != null) {
+                mEmojiAsDefaultStyleExceptions = new int[emojiAsDefaultStyleExceptions.size()];
+                int i = 0;
+                for (Integer exception : emojiAsDefaultStyleExceptions) {
+                    mEmojiAsDefaultStyleExceptions[i++] = exception;
+                }
+                Arrays.sort(mEmojiAsDefaultStyleExceptions);
+            } else {
+                mEmojiAsDefaultStyleExceptions = null;
+            }
+            return this;
+        }
+
+        /**
          * Determines whether a background will be drawn for the emojis that are found and
          * replaced by EmojiCompat. Should be used only for debugging purposes. The indicator color
          * can be set using {@link #setEmojiSpanIndicatorColor(int)}.
@@ -1020,7 +1084,9 @@
             }
 
             mMetadataRepo = metadataRepo;
-            mProcessor = new EmojiProcessor(mMetadataRepo, new SpanFactory());
+            mProcessor = new EmojiProcessor(mMetadataRepo, new SpanFactory(),
+                    mEmojiCompat.mUseEmojiAsDefaultStyle,
+                    mEmojiCompat.mEmojiAsDefaultStyleExceptions);
 
             mEmojiCompat.onMetadataLoadSuccess();
         }
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/EmojiProcessor.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
index 3feb36d..f711704 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiProcessor.java
@@ -22,6 +22,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.widget.SpannableBuilder;
@@ -40,6 +41,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Processes the CharSequence and adds the emojis.
@@ -90,14 +93,29 @@
      */
     private GlyphChecker mGlyphChecker = new GlyphChecker();
 
+    /**
+     * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean)
+     */
+    private final boolean mUseEmojiAsDefaultStyle;
+
+    /**
+     * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List)
+     */
+    private final int[] mEmojiAsDefaultStyleExceptions;
+
     EmojiProcessor(@NonNull final MetadataRepo metadataRepo,
-            @NonNull final EmojiCompat.SpanFactory spanFactory) {
+            @NonNull final EmojiCompat.SpanFactory spanFactory,
+            final boolean useEmojiAsDefaultStyle,
+            @Nullable final int[] emojiAsDefaultStyleExceptions) {
         mSpanFactory = spanFactory;
         mMetadataRepo = metadataRepo;
+        mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+        mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions;
     }
 
     EmojiMetadata getEmojiMetadata(@NonNull final CharSequence charSequence) {
-        final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
+        final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(),
+                mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions);
         final int end = charSequence.length();
         int currentOffset = 0;
 
@@ -189,7 +207,8 @@
             }
             // add new ones
             int addedCount = 0;
-            final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
+            final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(),
+                    mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions);
 
             int currentOffset = start;
             int codePoint = Character.codePointAt(charSequence, currentOffset);
@@ -483,9 +502,22 @@
          */
         private int mCurrentDepth;
 
-        ProcessorSm(MetadataRepo.Node rootNode) {
+        /**
+         * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean)
+         */
+        private final boolean mUseEmojiAsDefaultStyle;
+
+        /**
+         * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List)
+         */
+        private final int[] mEmojiAsDefaultStyleExceptions;
+
+        ProcessorSm(MetadataRepo.Node rootNode, boolean useEmojiAsDefaultStyle,
+                int[] emojiAsDefaultStyleExceptions) {
             mRootNode = rootNode;
             mCurrentNode = rootNode;
+            mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle;
+            mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions;
         }
 
         @Action
@@ -505,8 +537,7 @@
                             action = ACTION_ADVANCE_END;
                         } else if (mCurrentNode.getData() != null) {
                             if (mCurrentDepth == 1) {
-                                if (mCurrentNode.getData().isDefaultEmoji()
-                                        || isEmojiStyle(mLastCodepoint)) {
+                                if (shouldUseEmojiPresentationStyleForSingleCodepoint()) {
                                     mFlushNode = mCurrentNode;
                                     action = ACTION_FLUSH;
                                     reset();
@@ -571,9 +602,32 @@
          */
         boolean isInFlushableState() {
             return mState == STATE_WALKING && mCurrentNode.getData() != null
-                    && (mCurrentNode.getData().isDefaultEmoji()
-                    || isEmojiStyle(mLastCodepoint)
-                    || mCurrentDepth > 1);
+                    && (mCurrentDepth > 1 || shouldUseEmojiPresentationStyleForSingleCodepoint());
+        }
+
+        private boolean shouldUseEmojiPresentationStyleForSingleCodepoint() {
+            if (mCurrentNode.getData().isDefaultEmoji()) {
+                // The codepoint is emoji style by default.
+                return true;
+            }
+            if (isEmojiStyle(mLastCodepoint)) {
+                // The codepoint was followed by the emoji style variation selector.
+                return true;
+            }
+            if (mUseEmojiAsDefaultStyle) {
+                // Emoji presentation style for text style default emojis is enabled. We have
+                // to check that the current codepoint is not an exception.
+                if (mEmojiAsDefaultStyleExceptions == null) {
+                    return true;
+                }
+                final int codepoint = mCurrentNode.getData().getCodepointAt(0);
+                final int index = Arrays.binarySearch(mEmojiAsDefaultStyleExceptions, codepoint);
+                if (index < 0) {
+                    // Index is negative, so the codepoint was not found in the array of exceptions.
+                    return true;
+                }
+            }
+            return false;
         }
 
         /**
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/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
index 65afd9c..752e052 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiButton.java
@@ -15,9 +15,9 @@
  */
 package android.support.text.emoji.widget;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.text.InputFilter;
 import android.util.AttributeSet;
 import android.widget.Button;
@@ -50,7 +50,7 @@
         init();
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     public EmojiButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         init();
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
index 70ca7a6..e1057e8 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
@@ -15,11 +15,11 @@
  */
 package android.support.text.emoji.widget;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
 import android.support.annotation.IntRange;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.text.emoji.EmojiCompat;
 import android.text.method.KeyListener;
 import android.util.AttributeSet;
@@ -57,7 +57,7 @@
         init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     public EmojiEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         init(attrs, defStyleAttr, defStyleRes);
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
index 2e4d3ca..1d5e2dd 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -18,12 +18,12 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.inputmethodservice.ExtractEditText;
 import android.os.Build;
 import android.support.annotation.IntRange;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
 import android.support.text.emoji.EmojiSpan;
@@ -64,7 +64,7 @@
         init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     public EmojiExtractEditText(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
diff --git a/emoji/core/tests/java/android/support/text/emoji/AllEmojisTest.java b/emoji/core/tests/java/android/support/text/emoji/AllEmojisTest.java
index f1e2b0f..3110701 100644
--- a/emoji/core/tests/java/android/support/text/emoji/AllEmojisTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/AllEmojisTest.java
@@ -22,7 +22,6 @@
 
 import static org.junit.Assert.assertThat;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Paint;
 import android.support.test.InstrumentationRegistry;
@@ -51,7 +50,6 @@
 @SmallTest
 @RunWith(Parameterized.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class AllEmojisTest {
 
     /**
diff --git a/emoji/core/tests/java/android/support/text/emoji/ConfigTest.java b/emoji/core/tests/java/android/support/text/emoji/ConfigTest.java
index 538ce99..3a843b6 100644
--- a/emoji/core/tests/java/android/support/text/emoji/ConfigTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/ConfigTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Color;
 import android.support.test.InstrumentationRegistry;
@@ -70,7 +69,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testBuild_withDefaultValues() {
         final EmojiCompat.Config config = new ValidTestConfig().setReplaceAll(true);
 
@@ -98,7 +96,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19) //Fail callback never called for pre 19
-    @TargetApi(19)
     public void testInitCallback_callsFailCallback() {
         final EmojiCompat.InitCallback initCallback1 = mock(EmojiCompat.InitCallback.class);
         final EmojiCompat.InitCallback initCallback2 = mock(EmojiCompat.InitCallback.class);
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
index 0ce26e4..724f8a0 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiCompatTest.java
@@ -87,8 +87,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 @SmallTest
@@ -729,6 +731,58 @@
         assertFalse(EmojiCompat.handleOnKeyDown(editable, event.getKeyCode(), event));
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testUseEmojiAsDefaultStyle_whenEmojiInTheMiddle() {
+        final Config config = new TestConfig().setReplaceAll(true);
+        EmojiCompat.reset(config);
+        String s = new TestString(0x0061, CHAR_DEFAULT_TEXT_STYLE, 0x0062).toString();
+        // no span should be added as the emoji is text style presented by default
+        assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+        // a span should be added when we use the emoji style presentation as default
+        EmojiCompat.reset(config.setUseEmojiAsDefaultStyle(true));
+        assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 1, 2));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testUseEmojiAsDefaultStyle_whenEmojiAtTheEnd() {
+        final Config config = new TestConfig().setReplaceAll(true);
+        EmojiCompat.reset(config);
+        String s = new TestString(0x0061, CHAR_DEFAULT_TEXT_STYLE).toString();
+        // no span should be added as the emoji is text style presented by default
+        assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+        // a span should be added when we use the emoji style presentation as default
+        EmojiCompat.reset(config.setUseEmojiAsDefaultStyle(true));
+        assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 1, 2));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testUseEmojiAsDefaultStyle_noEmojisAdded_whenMarkedAsException() {
+        final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
+        final List<Integer> exceptions =
+                Arrays.asList(CHAR_DEFAULT_TEXT_STYLE + 1, CHAR_DEFAULT_TEXT_STYLE);
+        final Config config = new TestConfig().setReplaceAll(true)
+                .setUseEmojiAsDefaultStyle(true, exceptions);
+        EmojiCompat.reset(config);
+        // no span should be added as the text style codepoint is marked as exception
+        assertThat(EmojiCompat.get().process(s), not(hasEmoji()));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testUseEmojiAsDefaultStyle_emojisAdded_whenNotMarkedAsException() {
+        final String s = new TestString(CHAR_DEFAULT_TEXT_STYLE).toString();
+        final List<Integer> exceptions =
+                Arrays.asList(CHAR_DEFAULT_TEXT_STYLE - 1, CHAR_DEFAULT_TEXT_STYLE + 1);
+        final Config config = new TestConfig().setReplaceAll(true)
+                .setUseEmojiAsDefaultStyle(true, exceptions);
+        EmojiCompat.reset(config);
+        // a span should be added as the codepoint is not included in the set of exceptions
+        assertThat(EmojiCompat.get().process(s), hasEmojiAt(DEFAULT_TEXT_STYLE, 0, 1));
+    }
+
     private void assertCodePointMatch(EmojiMapping emoji) {
         assertCodePointMatch(emoji.id(), emoji.codepoints());
     }
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiSpanInstrumentationTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiSpanInstrumentationTest.java
index f30df8f..b1b3fa0 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiSpanInstrumentationTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiSpanInstrumentationTest.java
@@ -24,7 +24,6 @@
 
 import static org.junit.Assert.assertThat;
 
-import android.annotation.TargetApi;
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
@@ -47,7 +46,6 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiSpanInstrumentationTest {
 
     @Rule
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiSpanTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiSpanTest.java
index 816ab64..b97ebdc 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiSpanTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiSpanTest.java
@@ -25,7 +25,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.TargetApi;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
@@ -43,7 +42,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiSpanTest {
 
     @Before
diff --git a/emoji/core/tests/java/android/support/text/emoji/FontRequestEmojiCompatConfigTest.java b/emoji/core/tests/java/android/support/text/emoji/FontRequestEmojiCompatConfigTest.java
index ce2b098..692b963 100644
--- a/emoji/core/tests/java/android/support/text/emoji/FontRequestEmojiCompatConfigTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/FontRequestEmojiCompatConfigTest.java
@@ -39,7 +39,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
@@ -99,7 +98,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_whenGetFontThrowsException() throws NameNotFoundException {
         final Exception exception = new RuntimeException();
         doThrow(exception).when(mFontProviderHelper).fetchFonts(
@@ -115,7 +113,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_providerNotFound() throws NameNotFoundException {
         doThrow(new NameNotFoundException()).when(mFontProviderHelper).fetchFonts(
                 any(Context.class), any(FontRequest.class));
@@ -133,7 +130,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_wrongCertificate() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_WRONG_CERTIFICATES, null /* fonts */,
                 "fetchFonts failed (" + STATUS_WRONG_CERTIFICATES + ")");
@@ -141,23 +137,22 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_fontNotFound() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_NOT_FOUND),
                 "fetchFonts result is not OK. (" + RESULT_CODE_FONT_NOT_FOUND + ")");
     }
 
-    @Test@SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_fontUnavailable() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_UNAVAILABLE),
                 "fetchFonts result is not OK. (" + RESULT_CODE_FONT_UNAVAILABLE + ")");
     }
 
-    @Test@SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
     public void testLoad_malformedQuery() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK,
                 getTestFontInfoWithInvalidPath(RESULT_CODE_MALFORMED_QUERY),
@@ -166,7 +161,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_resultNotFound() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK, new FontInfo[] {},
                 "fetchFonts failed (empty result)");
@@ -174,7 +168,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_nullFontInfo() throws NameNotFoundException {
         verifyLoaderOnFailedCalled(STATUS_OK, null /* fonts */,
                 "fetchFonts failed (empty result)");
@@ -182,7 +175,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_cannotLoadTypeface() throws NameNotFoundException {
         // getTestFontInfoWithInvalidPath returns FontInfo with invalid path to file.
         verifyLoaderOnFailedCalled(STATUS_OK,
@@ -192,7 +184,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_success() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -212,7 +203,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_retryPolicy() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -235,7 +225,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_keepRetryingAndGiveUp() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final FontInfo[] fonts =  new FontInfo[] {
@@ -262,7 +251,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_keepRetryingAndFail() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -320,7 +308,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_keepRetryingAndSuccess() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -378,7 +365,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_ObserverNotifyAndSuccess() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
@@ -435,7 +421,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testLoad_ObserverNotifyAndFail() throws IOException, NameNotFoundException {
         final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf");
         final Uri uri = Uri.fromFile(file);
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
index 626bca1..5e99a01 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextHelperTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.TargetApi;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
@@ -47,7 +46,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiEditTextHelperTest {
     EmojiEditTextHelper mEmojiEditTextHelper;
     EditText mEditText;
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
index 3528095..0fd6394 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
@@ -24,7 +24,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 
-import android.annotation.TargetApi;
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
@@ -94,7 +93,6 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 19)
-    @TargetApi(19)
     public void testSetMaxCount() {
         final TestActivity activity = mActivityRule.getActivity();
         final EmojiEditText editText = activity.findViewById(R.id.editTextWithMaxCount);
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiInputConnectionTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiInputConnectionTest.java
index 0958978..6476898 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiInputConnectionTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiInputConnectionTest.java
@@ -30,7 +30,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
 import android.support.test.InstrumentationRegistry;
@@ -56,7 +55,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiInputConnectionTest {
 
     private InputConnection mInputConnection;
@@ -87,7 +85,6 @@
                 new EditorInfo());
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     private void setupDeleteSurroundingText() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             when(mInputConnection.deleteSurroundingTextInCodePoints(anyInt(), anyInt())).thenReturn(
@@ -123,7 +120,6 @@
         verify(mInputConnection, never()).deleteSurroundingText(anyInt(), anyInt());
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
     @Test
     public void testDeleteSurroundingTextInCodePoints_doesNotDelete() {
@@ -132,7 +128,6 @@
         verify(mInputConnection, times(1)).deleteSurroundingTextInCodePoints(1, 0);
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
     @Test
     public void testDeleteSurroundingTextInCodePoints_deletesEmojiBackward() {
@@ -141,7 +136,6 @@
         verify(mInputConnection, never()).deleteSurroundingTextInCodePoints(anyInt(), anyInt());
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
     @Test
     public void testDeleteSurroundingTextInCodePoints_deletesEmojiForward() {
@@ -150,7 +144,6 @@
         verify(mInputConnection, never()).deleteSurroundingTextInCodePoints(anyInt(), anyInt());
     }
 
-    @TargetApi(Build.VERSION_CODES.N)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
     @Test
     public void testDeleteSurroundingTextInCodePoints_doesNotDeleteEmojiIfSelectionAtStartIndex() {
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiKeyListenerTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiKeyListenerTest.java
index 06a5e68..081deb0 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiKeyListenerTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiKeyListenerTest.java
@@ -39,7 +39,6 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.annotation.TargetApi;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -61,7 +60,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiKeyListenerTest {
 
     private KeyListener mKeyListener;
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextViewHelperTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextViewHelperTest.java
index 9a5ccbd..b99c58e 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextViewHelperTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiTextViewHelperTest.java
@@ -26,7 +26,6 @@
 import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.mock;
 
-import android.annotation.TargetApi;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
@@ -45,7 +44,6 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = 19)
-@TargetApi(19)
 public class EmojiTextViewHelperTest {
     EmojiTextViewHelper mTextViewHelper;
     TextView mTextView;
diff --git a/exifinterface/Android.mk b/exifinterface/Android.mk
index 4e5bdc0..18ffcf3 100644
--- a/exifinterface/Android.mk
+++ b/exifinterface/Android.mk
@@ -25,7 +25,8 @@
 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 := \
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index 68815ae..e81d422 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)
 }
 
 supportLibrary {
@@ -19,5 +19,4 @@
     mavenGroup = LibraryGroups.SUPPORT
     inceptionYear = "2016"
     description = "Android Support ExifInterface"
-    legacySourceLocation = true
 }
diff --git a/exifinterface/tests/AndroidManifest.xml b/exifinterface/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from exifinterface/tests/AndroidManifest.xml
rename to exifinterface/src/androidTest/AndroidManifest.xml
diff --git a/exifinterface/tests/NO_DOCS b/exifinterface/src/androidTest/NO_DOCS
similarity index 100%
rename from exifinterface/tests/NO_DOCS
rename to exifinterface/src/androidTest/NO_DOCS
diff --git a/exifinterface/src/androidTest/java/android/support/media/ExifInterfaceTest.java b/exifinterface/src/androidTest/java/android/support/media/ExifInterfaceTest.java
new file mode 100644
index 0000000..034ba09
--- /dev/null
+++ b/exifinterface/src/androidTest/java/android/support/media/ExifInterfaceTest.java
@@ -0,0 +1,898 @@
+/*
+ * 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.media;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.location.Location;
+import android.os.Environment;
+import android.support.exifinterface.test.R;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.Pair;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+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.ByteBuffer;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link ExifInterface}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExifInterfaceTest {
+    private static final String TAG = ExifInterface.class.getSimpleName();
+    private static final boolean VERBOSE = false;  // lots of logging
+    private static final double DIFFERENCE_TOLERANCE = .001;
+
+    private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
+    private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
+    private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
+    private static final int[] IMAGE_RESOURCES = new int[] {
+            R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800};
+    private static final String[] IMAGE_FILENAMES = new String[] {
+            EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG};
+
+    private static final String TEST_TEMP_FILE_NAME = "testImage";
+    private static final double DELTA = 1e-8;
+    // We translate double to rational in a 1/10000 precision.
+    private static final double RATIONAL_DELTA = 0.0001;
+    private static final int TEST_LAT_LONG_VALUES_ARRAY_LENGTH = 8;
+    private static final int TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS = 30;
+    private static final double[] TEST_LATITUDE_VALID_VALUES = new double[]
+            {0, 45, 90, -60, 0.00000001, -89.999999999, 14.2465923626, -68.3434534737};
+    private static final double[] TEST_LONGITUDE_VALID_VALUES = new double[]
+            {0, -45, 90, -120, 180, 0.00000001, -179.99999999999, -58.57834236352};
+    private static final double[] TEST_LATITUDE_INVALID_VALUES = new double[]
+            {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 90.0000000001,
+                    263.34763236326, -1e5, 347.32525, -176.346347754};
+    private static final double[] TEST_LONGITUDE_INVALID_VALUES = new double[]
+            {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 180.0000000001,
+                    263.34763236326, -1e10, 347.325252623, -4000.346323236};
+    private static final double[] TEST_ALTITUDE_VALUES = new double[]
+            {0, -2000, 10000, -355.99999999999, 18.02038};
+    private static final int[][] TEST_ROTATION_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, -90, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 0, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 90, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 180, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 270, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_UNDEFINED, 540, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, -90, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_NORMAL, 0, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_NORMAL, 90, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_NORMAL, 180, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_NORMAL, 270, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_NORMAL, 540, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_90, -90, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, 0, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_90, 90, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_90, 180 , ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_90, 270, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, 540, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_180, -90, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_180, 0, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_180, 90, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_180, 180, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_180, 270, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_180, 540, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, -90, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_270, 0, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_ROTATE_270, 90, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, 180, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_ROTATE_270, 270, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_ROTATE_270, 540, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, -90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 0, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 180,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 270, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 540,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, -90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 0,
+                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 180,
+                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 270, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 540,
+                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, -90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 0, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 180, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 270, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, 540, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, -90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 0, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 180, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 270, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_TRANSVERSE, 540, ExifInterface.ORIENTATION_TRANSPOSE},
+    };
+    private static final int[][] TEST_FLIP_VERTICALLY_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_270},
+            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_90}
+    };
+    private static final int[][] TEST_FLIP_HORIZONTALLY_STATE_MACHINE = {
+            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
+            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
+            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE},
+            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL},
+            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE},
+            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_ROTATE_180},
+            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_NORMAL},
+            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_90},
+            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_270}
+    };
+    private static final HashMap<Integer, Pair> FLIP_STATE_AND_ROTATION_DEGREES = new HashMap<>();
+    static {
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_UNDEFINED, new Pair(false, 0));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_NORMAL, new Pair(false, 0));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_ROTATE_90, new Pair(false, 90));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_ROTATE_180, new Pair(false, 180));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_ROTATE_270, new Pair(false, 270));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_FLIP_HORIZONTAL, new Pair(true, 0));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_TRANSVERSE, new Pair(true, 90));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_FLIP_VERTICAL, new Pair(true, 180));
+        FLIP_STATE_AND_ROTATION_DEGREES.put(
+                ExifInterface.ORIENTATION_TRANSPOSE, new Pair(true, 270));
+    }
+
+    private static final String[] EXIF_TAGS = {
+            ExifInterface.TAG_MAKE,
+            ExifInterface.TAG_MODEL,
+            ExifInterface.TAG_F_NUMBER,
+            ExifInterface.TAG_DATETIME_ORIGINAL,
+            ExifInterface.TAG_EXPOSURE_TIME,
+            ExifInterface.TAG_FLASH,
+            ExifInterface.TAG_FOCAL_LENGTH,
+            ExifInterface.TAG_GPS_ALTITUDE,
+            ExifInterface.TAG_GPS_ALTITUDE_REF,
+            ExifInterface.TAG_GPS_DATESTAMP,
+            ExifInterface.TAG_GPS_LATITUDE,
+            ExifInterface.TAG_GPS_LATITUDE_REF,
+            ExifInterface.TAG_GPS_LONGITUDE,
+            ExifInterface.TAG_GPS_LONGITUDE_REF,
+            ExifInterface.TAG_GPS_PROCESSING_METHOD,
+            ExifInterface.TAG_GPS_TIMESTAMP,
+            ExifInterface.TAG_IMAGE_LENGTH,
+            ExifInterface.TAG_IMAGE_WIDTH,
+            ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
+            ExifInterface.TAG_ORIENTATION,
+            ExifInterface.TAG_WHITE_BALANCE
+    };
+
+    private static class ExpectedValue {
+        // Thumbnail information.
+        public final boolean hasThumbnail;
+        public final int thumbnailWidth;
+        public final int thumbnailHeight;
+
+        // GPS information.
+        public final boolean hasLatLong;
+        public final float latitude;
+        public final float longitude;
+        public final float altitude;
+
+        // Values.
+        public final String make;
+        public final String model;
+        public final float aperture;
+        public final String dateTimeOriginal;
+        public final float exposureTime;
+        public final float flash;
+        public final String focalLength;
+        public final String gpsAltitude;
+        public final String gpsAltitudeRef;
+        public final String gpsDatestamp;
+        public final String gpsLatitude;
+        public final String gpsLatitudeRef;
+        public final String gpsLongitude;
+        public final String gpsLongitudeRef;
+        public final String gpsProcessingMethod;
+        public final String gpsTimestamp;
+        public final int imageLength;
+        public final int imageWidth;
+        public final String iso;
+        public final int orientation;
+        public final int whiteBalance;
+
+        private static String getString(TypedArray typedArray, int index) {
+            String stringValue = typedArray.getString(index);
+            if (stringValue == null || stringValue.equals("")) {
+                return null;
+            }
+            return stringValue.trim();
+        }
+
+        ExpectedValue(TypedArray typedArray) {
+            // Reads thumbnail information.
+            hasThumbnail = typedArray.getBoolean(0, false);
+            thumbnailWidth = typedArray.getInt(1, 0);
+            thumbnailHeight = typedArray.getInt(2, 0);
+
+            // Reads GPS information.
+            hasLatLong = typedArray.getBoolean(3, false);
+            latitude = typedArray.getFloat(4, 0f);
+            longitude = typedArray.getFloat(5, 0f);
+            altitude = typedArray.getFloat(6, 0f);
+
+            // Reads values.
+            make = getString(typedArray, 7);
+            model = getString(typedArray, 8);
+            aperture = typedArray.getFloat(9, 0f);
+            dateTimeOriginal = getString(typedArray, 10);
+            exposureTime = typedArray.getFloat(11, 0f);
+            flash = typedArray.getFloat(12, 0f);
+            focalLength = getString(typedArray, 13);
+            gpsAltitude = getString(typedArray, 14);
+            gpsAltitudeRef = getString(typedArray, 15);
+            gpsDatestamp = getString(typedArray, 16);
+            gpsLatitude = getString(typedArray, 17);
+            gpsLatitudeRef = getString(typedArray, 18);
+            gpsLongitude = getString(typedArray, 19);
+            gpsLongitudeRef = getString(typedArray, 20);
+            gpsProcessingMethod = getString(typedArray, 21);
+            gpsTimestamp = getString(typedArray, 22);
+            imageLength = typedArray.getInt(23, 0);
+            imageWidth = typedArray.getInt(24, 0);
+            iso = getString(typedArray, 25);
+            orientation = typedArray.getInt(26, 0);
+            whiteBalance = typedArray.getInt(27, 0);
+
+            typedArray.recycle();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String outputPath =
+                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
+                            .getAbsolutePath();
+
+            InputStream inputStream = null;
+            FileOutputStream outputStream = null;
+            try {
+                inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
+                outputStream = new FileOutputStream(outputPath);
+                copy(inputStream, outputStream);
+            } finally {
+                closeQuietly(inputStream);
+                closeQuietly(outputStream);
+            }
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String imageFilePath =
+                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
+                            .getAbsolutePath();
+            File imageFile = new File(imageFilePath);
+            if (imageFile.exists()) {
+                imageFile.delete();
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
+        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
+    }
+
+    @Test
+    @LargeTest
+    public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
+        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
+    }
+
+    @Test
+    @LargeTest
+    public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
+        testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
+    }
+
+    @Test
+    @LargeTest
+    public void testDoNotFailOnCorruptedImage() throws Throwable {
+        // ExifInterface shouldn't raise any exceptions except an IOException when unable to open
+        // a file, even with a corrupted image. Generates randomly corrupted image stream for
+        // testing. Uses Epoch date count as random seed so that we can reproduce a broken test.
+        long seed = System.currentTimeMillis() / (86400 * 1000);
+        Log.d(TAG, "testDoNotFailOnCorruptedImage random seed: " + seed);
+        Random random = new Random(seed);
+        byte[] bytes = new byte[8096];
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        for (int i = 0; i < TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS; i++) {
+            buffer.clear();
+            random.nextBytes(bytes);
+            if (!randomlyCorrupted(random)) {
+                buffer.put(ExifInterface.JPEG_SIGNATURE);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.put(ExifInterface.MARKER_APP1);
+            }
+            buffer.putShort((short) (random.nextInt(100) + 300));
+            if (!randomlyCorrupted(random)) {
+                buffer.put(ExifInterface.IDENTIFIER_EXIF_APP1);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.putShort(ExifInterface.BYTE_ALIGN_MM);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.put((byte) 0);
+                buffer.put(ExifInterface.START_CODE);
+            }
+            buffer.putInt(8);
+
+            // Primary Tags
+            int numberOfDirectory = random.nextInt(8) + 1;
+            if (!randomlyCorrupted(random)) {
+                buffer.putShort((short) numberOfDirectory);
+            }
+            for (int j = 0; j < numberOfDirectory; j++) {
+                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PRIMARY, random);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.putInt(buffer.position() - 8);
+            }
+
+            // Thumbnail Tags
+            numberOfDirectory = random.nextInt(8) + 1;
+            if (!randomlyCorrupted(random)) {
+                buffer.putShort((short) numberOfDirectory);
+            }
+            for (int j = 0; j < numberOfDirectory; j++) {
+                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_THUMBNAIL, random);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.putInt(buffer.position() - 8);
+            }
+
+            // Preview Tags
+            numberOfDirectory = random.nextInt(8) + 1;
+            if (!randomlyCorrupted(random)) {
+                buffer.putShort((short) numberOfDirectory);
+            }
+            for (int j = 0; j < numberOfDirectory; j++) {
+                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PREVIEW, random);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.putInt(buffer.position() - 8);
+            }
+
+            if (!randomlyCorrupted(random)) {
+                buffer.put(ExifInterface.MARKER);
+            }
+            if (!randomlyCorrupted(random)) {
+                buffer.put(ExifInterface.MARKER_EOI);
+            }
+
+            try {
+                new ExifInterface(new ByteArrayInputStream(bytes));
+                // Always success
+            } catch (IOException e) {
+                fail("Should not reach here!");
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetGpsInfo() throws IOException {
+        final String provider = "ExifInterfaceTest";
+        final long timestamp = System.currentTimeMillis();
+        final float speedInMeterPerSec = 36.627533f;
+        Location location = new Location(provider);
+        location.setLatitude(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1]);
+        location.setLongitude(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1]);
+        location.setAltitude(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1]);
+        location.setSpeed(speedInMeterPerSec);
+        location.setTime(timestamp);
+        ExifInterface exif = createTestExifInterface();
+        exif.setGpsInfo(location);
+
+        double[] latLong = exif.getLatLong();
+        assertNotNull(latLong);
+        assertEquals(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1],
+                latLong[0], DELTA);
+        assertEquals(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1],
+                latLong[1], DELTA);
+        assertEquals(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1], exif.getAltitude(0),
+                RATIONAL_DELTA);
+        assertEquals("K", exif.getAttribute(ExifInterface.TAG_GPS_SPEED_REF));
+        assertEquals(speedInMeterPerSec, exif.getAttributeDouble(ExifInterface.TAG_GPS_SPEED, 0.0)
+                * 1000 / TimeUnit.HOURS.toSeconds(1), RATIONAL_DELTA);
+        assertEquals(provider, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+        // GPS time's precision is secs.
+        assertEquals(TimeUnit.MILLISECONDS.toSeconds(timestamp),
+                TimeUnit.MILLISECONDS.toSeconds(exif.getGpsDateTime()));
+    }
+
+    @Test
+    @SmallTest
+    public void testSetLatLong_withValidValues() throws IOException {
+        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
+            ExifInterface exif = createTestExifInterface();
+            exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]);
+
+            double[] latLong = exif.getLatLong();
+            assertNotNull(latLong);
+            assertEquals(TEST_LATITUDE_VALID_VALUES[i], latLong[0], DELTA);
+            assertEquals(TEST_LONGITUDE_VALID_VALUES[i], latLong[1], DELTA);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetLatLong_withInvalidLatitude() throws IOException {
+        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
+            ExifInterface exif = createTestExifInterface();
+            try {
+                exif.setLatLong(TEST_LATITUDE_INVALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            assertNull(exif.getLatLong());
+            assertLatLongValuesAreNotSet(exif);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetLatLong_withInvalidLongitude() throws IOException {
+        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
+            ExifInterface exif = createTestExifInterface();
+            try {
+                exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_INVALID_VALUES[i]);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            assertNull(exif.getLatLong());
+            assertLatLongValuesAreNotSet(exif);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetAltitude() throws IOException {
+        for (int i = 0; i < TEST_ALTITUDE_VALUES.length; i++) {
+            ExifInterface exif = createTestExifInterface();
+            exif.setAltitude(TEST_ALTITUDE_VALUES[i]);
+            assertEquals(TEST_ALTITUDE_VALUES[i], exif.getAltitude(Double.NaN), RATIONAL_DELTA);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetDateTime() throws IOException {
+        final String dateTimeValue = "2017:02:02 22:22:22";
+        final String dateTimeOriginalValue = "2017:01:01 11:11:11";
+
+        File imageFile = new File(
+                Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG);
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
+        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
+        exif.saveAttributes();
+
+        // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
+        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+
+        // Now remove the DATETIME value.
+        exif.setAttribute(ExifInterface.TAG_DATETIME, null);
+        exif.saveAttributes();
+
+        // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
+
+        long currentTimeStamp = System.currentTimeMillis();
+        exif.setDateTime(currentTimeStamp);
+        exif.saveAttributes();
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(currentTimeStamp, exif.getDateTime());
+    }
+
+    @Test
+    @SmallTest
+    public void testRotation() throws IOException {
+        File imageFile = new File(
+                Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG);
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+
+        int num;
+        // Test flip vertically.
+        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_FLIP_VERTICALLY_STATE_MACHINE[num][0]));
+            exif.flipVertically();
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
+                    TEST_FLIP_VERTICALLY_STATE_MACHINE[num][1]);
+
+        }
+
+        // Test flip horizontally.
+        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][0]));
+            exif.flipHorizontally();
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
+                    TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][1]);
+
+        }
+
+        // Test rotate by degrees
+        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                Integer.toString(ExifInterface.ORIENTATION_NORMAL));
+        try {
+            exif.rotate(108);
+            fail("Rotate with 108 degree should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        for (num = 0; num < TEST_ROTATION_STATE_MACHINE.length; num++) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                    Integer.toString(TEST_ROTATION_STATE_MACHINE[num][0]));
+            exif.rotate(TEST_ROTATION_STATE_MACHINE[num][1]);
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertIntTag(exif, ExifInterface.TAG_ORIENTATION, TEST_ROTATION_STATE_MACHINE[num][2]);
+        }
+
+        // Test get flip state and rotation degrees.
+        for (Integer key : FLIP_STATE_AND_ROTATION_DEGREES.keySet()) {
+            exif.setAttribute(ExifInterface.TAG_ORIENTATION, key.toString());
+            exif.saveAttributes();
+            exif = new ExifInterface(imageFile.getAbsolutePath());
+            assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).first, exif.isFlipped());
+            assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).second,
+                    exif.getRotationDegrees());
+        }
+
+        // Test reset the rotation.
+        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
+                Integer.toString(ExifInterface.ORIENTATION_FLIP_HORIZONTAL));
+        exif.resetOrientation();
+        exif.saveAttributes();
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertIntTag(exif, ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+
+    }
+
+    @Test
+    @SmallTest
+    public void testInterchangeabilityBetweenTwoIsoSpeedTags() throws IOException {
+        // Tests that two tags TAG_ISO_SPEED_RATINGS and TAG_PHOTOGRAPHIC_SENSITIVITY can be used
+        // interchangeably.
+        final String oldTag = ExifInterface.TAG_ISO_SPEED_RATINGS;
+        final String newTag = ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY;
+        final String isoValue = "50";
+
+        ExifInterface exif = createTestExifInterface();
+        exif.setAttribute(oldTag, isoValue);
+        assertEquals(isoValue, exif.getAttribute(oldTag));
+        assertEquals(isoValue, exif.getAttribute(newTag));
+
+        exif = createTestExifInterface();
+        exif.setAttribute(newTag, isoValue);
+        assertEquals(isoValue, exif.getAttribute(oldTag));
+        assertEquals(isoValue, exif.getAttribute(newTag));
+    }
+
+    private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
+        // Prints thumbnail information.
+        if (exifInterface.hasThumbnail()) {
+            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
+            if (thumbnailBytes != null) {
+                Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
+                Bitmap bitmap = exifInterface.getThumbnailBitmap();
+                if (bitmap == null) {
+                    Log.e(TAG, fileName + " Corrupted thumbnail!");
+                } else {
+                    Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
+                            + bitmap.getHeight());
+                }
+            } else {
+                Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
+                        + "A thumbnail is expected.");
+            }
+        } else {
+            if (exifInterface.getThumbnailBytes() != null) {
+                Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
+                        + "No thumbnail is expected.");
+            } else {
+                Log.v(TAG, fileName + " No thumbnail");
+            }
+        }
+
+        // Prints GPS information.
+        Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
+
+        double[] latLong = exifInterface.getLatLong();
+        if (latLong != null) {
+            Log.v(TAG, fileName + " Latitude = " + latLong[0]);
+            Log.v(TAG, fileName + " Longitude = " + latLong[1]);
+        } else {
+            Log.v(TAG, fileName + " No latlong data");
+        }
+
+        // Prints values.
+        for (String tagKey : EXIF_TAGS) {
+            String tagValue = exifInterface.getAttribute(tagKey);
+            Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
+        }
+    }
+
+    private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
+        int intValue = exifInterface.getAttributeInt(tag, 0);
+        assertEquals(expectedValue, intValue);
+    }
+
+    private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
+        double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
+        assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
+    }
+
+    private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
+        String stringValue = exifInterface.getAttribute(tag);
+        if (stringValue != null) {
+            stringValue = stringValue.trim();
+        }
+        stringValue = ("".equals(stringValue)) ? null : stringValue;
+
+        assertEquals(expectedValue, stringValue);
+    }
+
+    private void compareWithExpectedValue(ExifInterface exifInterface,
+            ExpectedValue expectedValue, String verboseTag) {
+        if (VERBOSE) {
+            printExifTagsAndValues(verboseTag, exifInterface);
+        }
+        // Checks a thumbnail image.
+        assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
+        if (expectedValue.hasThumbnail) {
+            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
+            assertNotNull(thumbnailBytes);
+            Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
+            assertNotNull(thumbnailBitmap);
+            assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+            assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+        } else {
+            assertNull(exifInterface.getThumbnail());
+        }
+
+        // Checks GPS information.
+        double[] latLong = exifInterface.getLatLong();
+        assertEquals(expectedValue.hasLatLong, latLong != null);
+        if (expectedValue.hasLatLong) {
+            assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
+            assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
+        }
+        assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
+
+        // Checks values.
+        assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
+        assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
+        assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
+        assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
+                expectedValue.dateTimeOriginal);
+        assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
+        assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
+        assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
+                expectedValue.gpsAltitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
+                expectedValue.gpsLatitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
+                expectedValue.gpsLongitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
+                expectedValue.gpsProcessingMethod);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
+        assertStringTag(exifInterface, ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
+                expectedValue.iso);
+        assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
+        assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
+    }
+
+    private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
+            throws IOException {
+        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+        String verboseTag = imageFile.getName();
+
+        // Creates via path.
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        assertNotNull(exifInterface);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+        InputStream in = null;
+        // Creates via InputStream.
+        try {
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            exifInterface = new ExifInterface(in);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+        } finally {
+            closeQuietly(in);
+        }
+    }
+
+    private void testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue)
+            throws IOException {
+        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+        String verboseTag = imageFile.getName();
+
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+
+        // Test for modifying one attribute.
+        String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+        // Restore the backup value.
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+    }
+
+    private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        // Test for reading from external data storage.
+        testExifInterfaceCommon(fileName, expectedValue);
+
+        // Test for saving attributes.
+        testSaveAttributes_withFileName(fileName, expectedValue);
+    }
+
+    private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        // Test for reading from external data storage.
+        testExifInterfaceCommon(fileName, expectedValue);
+
+        // Since ExifInterface does not support for saving attributes for RAW files, do not test
+        // about writing back in here.
+    }
+
+    private void generateRandomExifTag(ByteBuffer buffer, int ifdType, Random random) {
+        ExifInterface.ExifTag[] tagGroup = ExifInterface.EXIF_TAGS[ifdType];
+        ExifInterface.ExifTag tag = tagGroup[random.nextInt(tagGroup.length)];
+        if (!randomlyCorrupted(random)) {
+            buffer.putShort((short) tag.number);
+        }
+        int dataFormat = random.nextInt(ExifInterface.IFD_FORMAT_NAMES.length);
+        if (!randomlyCorrupted(random)) {
+            buffer.putShort((short) dataFormat);
+        }
+        buffer.putInt(1);
+        int dataLength = ExifInterface.IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
+        if (dataLength > 4) {
+            buffer.putShort((short) random.nextInt(8096 - dataLength));
+            buffer.position(buffer.position() + 2);
+        } else {
+            buffer.position(buffer.position() + 4);
+        }
+    }
+
+    private boolean randomlyCorrupted(Random random) {
+        // Corrupts somewhere in a possibility of 1/500.
+        return random.nextInt(500) == 0;
+    }
+
+    private void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    private int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
+    private void assertLatLongValuesAreNotSet(ExifInterface exif) {
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+    }
+
+    private ExifInterface createTestExifInterface() throws IOException {
+        File image = File.createTempFile(TEST_TEMP_FILE_NAME, ".jpg");
+        image.deleteOnExit();
+        return new ExifInterface(image.getAbsolutePath());
+    }
+}
diff --git a/exifinterface/tests/res/raw/image_exif_byte_order_ii.jpg b/exifinterface/src/androidTest/res/raw/image_exif_byte_order_ii.jpg
similarity index 100%
rename from exifinterface/tests/res/raw/image_exif_byte_order_ii.jpg
rename to exifinterface/src/androidTest/res/raw/image_exif_byte_order_ii.jpg
Binary files differ
diff --git a/exifinterface/tests/res/raw/image_exif_byte_order_mm.jpg b/exifinterface/src/androidTest/res/raw/image_exif_byte_order_mm.jpg
similarity index 100%
rename from exifinterface/tests/res/raw/image_exif_byte_order_mm.jpg
rename to exifinterface/src/androidTest/res/raw/image_exif_byte_order_mm.jpg
Binary files differ
diff --git a/exifinterface/tests/res/raw/lg_g4_iso_800.dng b/exifinterface/src/androidTest/res/raw/lg_g4_iso_800.dng
similarity index 100%
rename from exifinterface/tests/res/raw/lg_g4_iso_800.dng
rename to exifinterface/src/androidTest/res/raw/lg_g4_iso_800.dng
Binary files differ
diff --git a/exifinterface/tests/res/values/arrays.xml b/exifinterface/src/androidTest/res/values/arrays.xml
similarity index 100%
rename from exifinterface/tests/res/values/arrays.xml
rename to exifinterface/src/androidTest/res/values/arrays.xml
diff --git a/exifinterface/AndroidManifest.xml b/exifinterface/src/main/AndroidManifest.xml
similarity index 100%
rename from exifinterface/AndroidManifest.xml
rename to exifinterface/src/main/AndroidManifest.xml
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/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java b/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
deleted file mode 100644
index 8836334..0000000
--- a/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
+++ /dev/null
@@ -1,898 +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.media;
-
-import static android.support.test.InstrumentationRegistry.getContext;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.fail;
-
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.location.Location;
-import android.os.Environment;
-import android.support.exifinterface.test.R;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-import android.util.Pair;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.Closeable;
-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.ByteBuffer;
-import java.util.HashMap;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link ExifInterface}.
- */
-@RunWith(AndroidJUnit4.class)
-public class ExifInterfaceTest {
-    private static final String TAG = ExifInterface.class.getSimpleName();
-    private static final boolean VERBOSE = false;  // lots of logging
-    private static final double DIFFERENCE_TOLERANCE = .001;
-
-    private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
-    private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
-    private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
-    private static final int[] IMAGE_RESOURCES = new int[] {
-            R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800};
-    private static final String[] IMAGE_FILENAMES = new String[] {
-            EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG};
-
-    private static final String TEST_TEMP_FILE_NAME = "testImage";
-    private static final double DELTA = 1e-8;
-    // We translate double to rational in a 1/10000 precision.
-    private static final double RATIONAL_DELTA = 0.0001;
-    private static final int TEST_LAT_LONG_VALUES_ARRAY_LENGTH = 8;
-    private static final int TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS = 30;
-    private static final double[] TEST_LATITUDE_VALID_VALUES = new double[]
-            {0, 45, 90, -60, 0.00000001, -89.999999999, 14.2465923626, -68.3434534737};
-    private static final double[] TEST_LONGITUDE_VALID_VALUES = new double[]
-            {0, -45, 90, -120, 180, 0.00000001, -179.99999999999, -58.57834236352};
-    private static final double[] TEST_LATITUDE_INVALID_VALUES = new double[]
-            {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 90.0000000001,
-                    263.34763236326, -1e5, 347.32525, -176.346347754};
-    private static final double[] TEST_LONGITUDE_INVALID_VALUES = new double[]
-            {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 180.0000000001,
-                    263.34763236326, -1e10, 347.325252623, -4000.346323236};
-    private static final double[] TEST_ALTITUDE_VALUES = new double[]
-            {0, -2000, 10000, -355.99999999999, 18.02038};
-    private static final int[][] TEST_ROTATION_STATE_MACHINE = {
-            {ExifInterface.ORIENTATION_UNDEFINED, -90, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_UNDEFINED, 0, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_UNDEFINED, 90, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_UNDEFINED, 180, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_UNDEFINED, 270, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_UNDEFINED, 540, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_NORMAL, -90, ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_NORMAL, 0, ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_NORMAL, 90, ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_NORMAL, 180,ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_NORMAL, 270,ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_NORMAL, 540,ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_ROTATE_90, -90, ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_ROTATE_90, 0, ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_ROTATE_90, 90, ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_ROTATE_90, 180,ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_ROTATE_90, 270,ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_ROTATE_90, 540,ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_ROTATE_180, -90, ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_ROTATE_180, 0, ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_ROTATE_180, 90, ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_ROTATE_180, 180,ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_ROTATE_180, 270,ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_ROTATE_180, 540,ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_ROTATE_270, -90, ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_ROTATE_270, 0, ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_ROTATE_270, 90, ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_ROTATE_270, 180,ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_ROTATE_270, 270,ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_ROTATE_270, 540,ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, -90, ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 0, ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 90, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 180,
-                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 270,ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, 540,
-                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, -90, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 0,
-                    ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 90, ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 180,
-                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 270, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, 540,
-                    ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_TRANSPOSE, -90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_TRANSPOSE, 0, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_TRANSPOSE, 90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_TRANSPOSE, 180,ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_TRANSPOSE, 270,ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_TRANSPOSE, 540,ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_TRANSVERSE, -90, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_TRANSVERSE, 0, ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_TRANSVERSE, 90, ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_TRANSVERSE, 180,ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_TRANSVERSE, 270,ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_TRANSVERSE, 540,ExifInterface.ORIENTATION_TRANSPOSE},
-    };
-    private static final int[][] TEST_FLIP_VERTICALLY_STATE_MACHINE = {
-            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_270},
-            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_90}
-    };
-    private static final int[][] TEST_FLIP_HORIZONTALLY_STATE_MACHINE = {
-            {ExifInterface.ORIENTATION_UNDEFINED, ExifInterface.ORIENTATION_UNDEFINED},
-            {ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_FLIP_HORIZONTAL},
-            {ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE},
-            {ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL},
-            {ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE},
-            {ExifInterface.ORIENTATION_FLIP_VERTICAL, ExifInterface.ORIENTATION_ROTATE_180},
-            {ExifInterface.ORIENTATION_FLIP_HORIZONTAL, ExifInterface.ORIENTATION_NORMAL},
-            {ExifInterface.ORIENTATION_TRANSPOSE, ExifInterface.ORIENTATION_ROTATE_90},
-            {ExifInterface.ORIENTATION_TRANSVERSE, ExifInterface.ORIENTATION_ROTATE_270}
-    };
-    private static final HashMap<Integer, Pair> FLIP_STATE_AND_ROTATION_DEGREES = new HashMap<>();
-    static {
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_UNDEFINED, new Pair(false, 0));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_NORMAL, new Pair(false, 0));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_ROTATE_90, new Pair(false, 90));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_ROTATE_180, new Pair(false, 180));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_ROTATE_270, new Pair(false, 270));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_FLIP_HORIZONTAL, new Pair(true, 0));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_TRANSVERSE, new Pair(true, 90));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_FLIP_VERTICAL, new Pair(true, 180));
-        FLIP_STATE_AND_ROTATION_DEGREES.put(
-                ExifInterface.ORIENTATION_TRANSPOSE, new Pair(true, 270));
-    }
-
-    private static final String[] EXIF_TAGS = {
-            ExifInterface.TAG_MAKE,
-            ExifInterface.TAG_MODEL,
-            ExifInterface.TAG_F_NUMBER,
-            ExifInterface.TAG_DATETIME_ORIGINAL,
-            ExifInterface.TAG_EXPOSURE_TIME,
-            ExifInterface.TAG_FLASH,
-            ExifInterface.TAG_FOCAL_LENGTH,
-            ExifInterface.TAG_GPS_ALTITUDE,
-            ExifInterface.TAG_GPS_ALTITUDE_REF,
-            ExifInterface.TAG_GPS_DATESTAMP,
-            ExifInterface.TAG_GPS_LATITUDE,
-            ExifInterface.TAG_GPS_LATITUDE_REF,
-            ExifInterface.TAG_GPS_LONGITUDE,
-            ExifInterface.TAG_GPS_LONGITUDE_REF,
-            ExifInterface.TAG_GPS_PROCESSING_METHOD,
-            ExifInterface.TAG_GPS_TIMESTAMP,
-            ExifInterface.TAG_IMAGE_LENGTH,
-            ExifInterface.TAG_IMAGE_WIDTH,
-            ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
-            ExifInterface.TAG_ORIENTATION,
-            ExifInterface.TAG_WHITE_BALANCE
-    };
-
-    private static class ExpectedValue {
-        // Thumbnail information.
-        public final boolean hasThumbnail;
-        public final int thumbnailWidth;
-        public final int thumbnailHeight;
-
-        // GPS information.
-        public final boolean hasLatLong;
-        public final float latitude;
-        public final float longitude;
-        public final float altitude;
-
-        // Values.
-        public final String make;
-        public final String model;
-        public final float aperture;
-        public final String dateTimeOriginal;
-        public final float exposureTime;
-        public final float flash;
-        public final String focalLength;
-        public final String gpsAltitude;
-        public final String gpsAltitudeRef;
-        public final String gpsDatestamp;
-        public final String gpsLatitude;
-        public final String gpsLatitudeRef;
-        public final String gpsLongitude;
-        public final String gpsLongitudeRef;
-        public final String gpsProcessingMethod;
-        public final String gpsTimestamp;
-        public final int imageLength;
-        public final int imageWidth;
-        public final String iso;
-        public final int orientation;
-        public final int whiteBalance;
-
-        private static String getString(TypedArray typedArray, int index) {
-            String stringValue = typedArray.getString(index);
-            if (stringValue == null || stringValue.equals("")) {
-                return null;
-            }
-            return stringValue.trim();
-        }
-
-        public ExpectedValue(TypedArray typedArray) {
-            // Reads thumbnail information.
-            hasThumbnail = typedArray.getBoolean(0, false);
-            thumbnailWidth = typedArray.getInt(1, 0);
-            thumbnailHeight = typedArray.getInt(2, 0);
-
-            // Reads GPS information.
-            hasLatLong = typedArray.getBoolean(3, false);
-            latitude = typedArray.getFloat(4, 0f);
-            longitude = typedArray.getFloat(5, 0f);
-            altitude = typedArray.getFloat(6, 0f);
-
-            // Reads values.
-            make = getString(typedArray, 7);
-            model = getString(typedArray, 8);
-            aperture = typedArray.getFloat(9, 0f);
-            dateTimeOriginal = getString(typedArray, 10);
-            exposureTime = typedArray.getFloat(11, 0f);
-            flash = typedArray.getFloat(12, 0f);
-            focalLength = getString(typedArray, 13);
-            gpsAltitude = getString(typedArray, 14);
-            gpsAltitudeRef = getString(typedArray, 15);
-            gpsDatestamp = getString(typedArray, 16);
-            gpsLatitude = getString(typedArray, 17);
-            gpsLatitudeRef = getString(typedArray, 18);
-            gpsLongitude = getString(typedArray, 19);
-            gpsLongitudeRef = getString(typedArray, 20);
-            gpsProcessingMethod = getString(typedArray, 21);
-            gpsTimestamp = getString(typedArray, 22);
-            imageLength = typedArray.getInt(23, 0);
-            imageWidth = typedArray.getInt(24, 0);
-            iso = getString(typedArray, 25);
-            orientation = typedArray.getInt(26, 0);
-            whiteBalance = typedArray.getInt(27, 0);
-
-            typedArray.recycle();
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String outputPath =
-                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
-                            .getAbsolutePath();
-
-            InputStream inputStream = null;
-            FileOutputStream outputStream = null;
-            try {
-                inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
-                outputStream = new FileOutputStream(outputPath);
-                copy(inputStream, outputStream);
-            } finally {
-                closeQuietly(inputStream);
-                closeQuietly(outputStream);
-            }
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String imageFilePath =
-                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
-                            .getAbsolutePath();
-            File imageFile = new File(imageFilePath);
-            if (imageFile.exists()) {
-                imageFile.delete();
-            }
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
-        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
-    }
-
-    @Test
-    @LargeTest
-    public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
-        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
-    }
-
-    @Test
-    @LargeTest
-    public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
-        testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
-    }
-
-    @Test
-    @LargeTest
-    public void testDoNotFailOnCorruptedImage() throws Throwable {
-        // ExifInterface shouldn't raise any exceptions except an IOException when unable to open
-        // a file, even with a corrupted image. Generates randomly corrupted image stream for
-        // testing. Uses Epoch date count as random seed so that we can reproduce a broken test.
-        long seed = System.currentTimeMillis() / (86400 * 1000);
-        Log.d(TAG, "testDoNotFailOnCorruptedImage random seed: " + seed);
-        Random random = new Random(seed);
-        byte[] bytes = new byte[8096];
-        ByteBuffer buffer = ByteBuffer.wrap(bytes);
-        for (int i = 0; i < TEST_NUMBER_OF_CORRUPTED_IMAGE_STREAMS; i++) {
-            buffer.clear();
-            random.nextBytes(bytes);
-            if (!randomlyCorrupted(random)) {
-                buffer.put(ExifInterface.JPEG_SIGNATURE);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.put(ExifInterface.MARKER_APP1);
-            }
-            buffer.putShort((short) (random.nextInt(100) + 300));
-            if (!randomlyCorrupted(random)) {
-                buffer.put(ExifInterface.IDENTIFIER_EXIF_APP1);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.putShort(ExifInterface.BYTE_ALIGN_MM);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.put((byte) 0);
-                buffer.put(ExifInterface.START_CODE);
-            }
-            buffer.putInt(8);
-
-            // Primary Tags
-            int numberOfDirectory = random.nextInt(8) + 1;
-            if (!randomlyCorrupted(random)) {
-                buffer.putShort((short) numberOfDirectory);
-            }
-            for (int j = 0; j < numberOfDirectory; j++) {
-                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PRIMARY, random);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.putInt(buffer.position() - 8);
-            }
-
-            // Thumbnail Tags
-            numberOfDirectory = random.nextInt(8) + 1;
-            if (!randomlyCorrupted(random)) {
-                buffer.putShort((short) numberOfDirectory);
-            }
-            for (int j = 0; j < numberOfDirectory; j++) {
-                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_THUMBNAIL, random);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.putInt(buffer.position() - 8);
-            }
-
-            // Preview Tags
-            numberOfDirectory = random.nextInt(8) + 1;
-            if (!randomlyCorrupted(random)) {
-                buffer.putShort((short) numberOfDirectory);
-            }
-            for (int j = 0; j < numberOfDirectory; j++) {
-                generateRandomExifTag(buffer, ExifInterface.IFD_TYPE_PREVIEW, random);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.putInt(buffer.position() - 8);
-            }
-
-            if (!randomlyCorrupted(random)) {
-                buffer.put(ExifInterface.MARKER);
-            }
-            if (!randomlyCorrupted(random)) {
-                buffer.put(ExifInterface.MARKER_EOI);
-            }
-
-            try {
-                new ExifInterface(new ByteArrayInputStream(bytes));
-                // Always success
-            } catch (IOException e) {
-                fail("Should not reach here!");
-            }
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetGpsInfo() throws IOException {
-        final String provider = "ExifInterfaceTest";
-        final long timestamp = System.currentTimeMillis();
-        final float speedInMeterPerSec = 36.627533f;
-        Location location = new Location(provider);
-        location.setLatitude(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1]);
-        location.setLongitude(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1]);
-        location.setAltitude(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1]);
-        location.setSpeed(speedInMeterPerSec);
-        location.setTime(timestamp);
-        ExifInterface exif = createTestExifInterface();
-        exif.setGpsInfo(location);
-
-        double[] latLong = exif.getLatLong();
-        assertNotNull(latLong);
-        assertEquals(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1],
-                latLong[0], DELTA);
-        assertEquals(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1],
-                latLong[1], DELTA);
-        assertEquals(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1], exif.getAltitude(0),
-                RATIONAL_DELTA);
-        assertEquals("K", exif.getAttribute(ExifInterface.TAG_GPS_SPEED_REF));
-        assertEquals(speedInMeterPerSec, exif.getAttributeDouble(ExifInterface.TAG_GPS_SPEED, 0.0)
-                * 1000 / TimeUnit.HOURS.toSeconds(1), RATIONAL_DELTA);
-        assertEquals(provider, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
-        // GPS time's precision is secs.
-        assertEquals(TimeUnit.MILLISECONDS.toSeconds(timestamp),
-                TimeUnit.MILLISECONDS.toSeconds(exif.getGpsDateTime()));
-    }
-
-    @Test
-    @SmallTest
-    public void testSetLatLong_withValidValues() throws IOException {
-        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
-            ExifInterface exif = createTestExifInterface();
-            exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]);
-
-            double[] latLong = exif.getLatLong();
-            assertNotNull(latLong);
-            assertEquals(TEST_LATITUDE_VALID_VALUES[i], latLong[0], DELTA);
-            assertEquals(TEST_LONGITUDE_VALID_VALUES[i], latLong[1], DELTA);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetLatLong_withInvalidLatitude() throws IOException {
-        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
-            ExifInterface exif = createTestExifInterface();
-            try {
-                exif.setLatLong(TEST_LATITUDE_INVALID_VALUES[i], TEST_LONGITUDE_VALID_VALUES[i]);
-                fail();
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            assertNull(exif.getLatLong());
-            assertLatLongValuesAreNotSet(exif);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetLatLong_withInvalidLongitude() throws IOException {
-        for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
-            ExifInterface exif = createTestExifInterface();
-            try {
-                exif.setLatLong(TEST_LATITUDE_VALID_VALUES[i], TEST_LONGITUDE_INVALID_VALUES[i]);
-                fail();
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            assertNull(exif.getLatLong());
-            assertLatLongValuesAreNotSet(exif);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetAltitude() throws IOException {
-        for (int i = 0; i < TEST_ALTITUDE_VALUES.length; i++) {
-            ExifInterface exif = createTestExifInterface();
-            exif.setAltitude(TEST_ALTITUDE_VALUES[i]);
-            assertEquals(TEST_ALTITUDE_VALUES[i], exif.getAltitude(Double.NaN), RATIONAL_DELTA);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetDateTime() throws IOException {
-        final String dateTimeValue = "2017:02:02 22:22:22";
-        final String dateTimeOriginalValue = "2017:01:01 11:11:11";
-
-        File imageFile = new File(
-                Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG);
-        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-        exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
-        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
-        exif.saveAttributes();
-
-        // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
-        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
-
-        // Now remove the DATETIME value.
-        exif.setAttribute(ExifInterface.TAG_DATETIME, null);
-        exif.saveAttributes();
-
-        // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
-
-        long currentTimeStamp = System.currentTimeMillis();
-        exif.setDateTime(currentTimeStamp);
-        exif.saveAttributes();
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(currentTimeStamp, exif.getDateTime());
-    }
-
-    @Test
-    @SmallTest
-    public void testRotation() throws IOException {
-        File imageFile = new File(
-                Environment.getExternalStorageDirectory(), EXIF_BYTE_ORDER_II_JPEG);
-        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-
-        int num;
-        // Test flip vertically.
-        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
-            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                    Integer.toString(TEST_FLIP_VERTICALLY_STATE_MACHINE[num][0]));
-            exif.flipVertically();
-            exif.saveAttributes();
-            exif = new ExifInterface(imageFile.getAbsolutePath());
-            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
-                    TEST_FLIP_VERTICALLY_STATE_MACHINE[num][1]);
-
-        }
-
-        // Test flip horizontally.
-        for (num = 0; num < TEST_FLIP_VERTICALLY_STATE_MACHINE.length; num++) {
-            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                    Integer.toString(TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][0]));
-            exif.flipHorizontally();
-            exif.saveAttributes();
-            exif = new ExifInterface(imageFile.getAbsolutePath());
-            assertIntTag(exif, ExifInterface.TAG_ORIENTATION,
-                    TEST_FLIP_HORIZONTALLY_STATE_MACHINE[num][1]);
-
-        }
-
-        // Test rotate by degrees
-        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                Integer.toString(ExifInterface.ORIENTATION_NORMAL));
-        try {
-            exif.rotate(108);
-            fail("Rotate with 108 degree should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Success
-        }
-
-        for (num = 0; num < TEST_ROTATION_STATE_MACHINE.length; num++) {
-            exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                    Integer.toString(TEST_ROTATION_STATE_MACHINE[num][0]));
-            exif.rotate(TEST_ROTATION_STATE_MACHINE[num][1]);
-            exif.saveAttributes();
-            exif = new ExifInterface(imageFile.getAbsolutePath());
-            assertIntTag(exif, ExifInterface.TAG_ORIENTATION, TEST_ROTATION_STATE_MACHINE[num][2]);
-        }
-
-        // Test get flip state and rotation degrees.
-        for (Integer key : FLIP_STATE_AND_ROTATION_DEGREES.keySet()) {
-            exif.setAttribute(ExifInterface.TAG_ORIENTATION, key.toString());
-            exif.saveAttributes();
-            exif = new ExifInterface(imageFile.getAbsolutePath());
-            assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).first, exif.isFlipped());
-            assertEquals(FLIP_STATE_AND_ROTATION_DEGREES.get(key).second,
-                    exif.getRotationDegrees());
-        }
-
-        // Test reset the rotation.
-        exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                Integer.toString(ExifInterface.ORIENTATION_FLIP_HORIZONTAL));
-        exif.resetOrientation();
-        exif.saveAttributes();
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertIntTag(exif, ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
-
-    }
-
-    @Test
-    @SmallTest
-    public void testInterchangeabilityBetweenTwoIsoSpeedTags() throws IOException {
-        // Tests that two tags TAG_ISO_SPEED_RATINGS and TAG_PHOTOGRAPHIC_SENSITIVITY can be used
-        // interchangeably.
-        final String oldTag = ExifInterface.TAG_ISO_SPEED_RATINGS;
-        final String newTag = ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY;
-        final String isoValue = "50";
-
-        ExifInterface exif = createTestExifInterface();
-        exif.setAttribute(oldTag, isoValue);
-        assertEquals(isoValue, exif.getAttribute(oldTag));
-        assertEquals(isoValue, exif.getAttribute(newTag));
-
-        exif = createTestExifInterface();
-        exif.setAttribute(newTag, isoValue);
-        assertEquals(isoValue, exif.getAttribute(oldTag));
-        assertEquals(isoValue, exif.getAttribute(newTag));
-    }
-
-    private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
-        // Prints thumbnail information.
-        if (exifInterface.hasThumbnail()) {
-            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
-            if (thumbnailBytes != null) {
-                Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
-                Bitmap bitmap = exifInterface.getThumbnailBitmap();
-                if (bitmap == null) {
-                    Log.e(TAG, fileName + " Corrupted thumbnail!");
-                } else {
-                    Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
-                            + bitmap.getHeight());
-                }
-            } else {
-                Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
-                        + "A thumbnail is expected.");
-            }
-        } else {
-            if (exifInterface.getThumbnailBytes() != null) {
-                Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
-                        + "No thumbnail is expected.");
-            } else {
-                Log.v(TAG, fileName + " No thumbnail");
-            }
-        }
-
-        // Prints GPS information.
-        Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
-
-        double[] latLong = exifInterface.getLatLong();
-        if (latLong != null) {
-            Log.v(TAG, fileName + " Latitude = " + latLong[0]);
-            Log.v(TAG, fileName + " Longitude = " + latLong[1]);
-        } else {
-            Log.v(TAG, fileName + " No latlong data");
-        }
-
-        // Prints values.
-        for (String tagKey : EXIF_TAGS) {
-            String tagValue = exifInterface.getAttribute(tagKey);
-            Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
-        }
-    }
-
-    private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
-        int intValue = exifInterface.getAttributeInt(tag, 0);
-        assertEquals(expectedValue, intValue);
-    }
-
-    private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
-        double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
-        assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
-    }
-
-    private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
-        String stringValue = exifInterface.getAttribute(tag);
-        if (stringValue != null) {
-            stringValue = stringValue.trim();
-        }
-        stringValue = (stringValue == "") ? null : stringValue;
-
-        assertEquals(expectedValue, stringValue);
-    }
-
-    private void compareWithExpectedValue(ExifInterface exifInterface,
-            ExpectedValue expectedValue, String verboseTag) {
-        if (VERBOSE) {
-            printExifTagsAndValues(verboseTag, exifInterface);
-        }
-        // Checks a thumbnail image.
-        assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
-        if (expectedValue.hasThumbnail) {
-            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
-            assertNotNull(thumbnailBytes);
-            Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
-            assertNotNull(thumbnailBitmap);
-            assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
-            assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
-        } else {
-            assertNull(exifInterface.getThumbnail());
-        }
-
-        // Checks GPS information.
-        double[] latLong = exifInterface.getLatLong();
-        assertEquals(expectedValue.hasLatLong, latLong != null);
-        if (expectedValue.hasLatLong) {
-            assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
-            assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
-        }
-        assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
-
-        // Checks values.
-        assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
-        assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
-        assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
-        assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
-                expectedValue.dateTimeOriginal);
-        assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
-        assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
-        assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
-                expectedValue.gpsAltitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
-                expectedValue.gpsLatitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
-                expectedValue.gpsLongitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
-                expectedValue.gpsProcessingMethod);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
-        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
-        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
-        assertStringTag(exifInterface, ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
-                expectedValue.iso);
-        assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
-        assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
-    }
-
-    private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
-            throws IOException {
-        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
-        String verboseTag = imageFile.getName();
-
-        // Creates via path.
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        assertNotNull(exifInterface);
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
-
-        InputStream in = null;
-        // Creates via InputStream.
-        try {
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
-            exifInterface = new ExifInterface(in);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
-        } finally {
-            closeQuietly(in);
-        }
-    }
-
-    private void testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue)
-            throws IOException {
-        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
-        String verboseTag = imageFile.getName();
-
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
-
-        // Test for modifying one attribute.
-        String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
-        // Restore the backup value.
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
-    }
-
-    private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
-            throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-
-        // Test for reading from external data storage.
-        testExifInterfaceCommon(fileName, expectedValue);
-
-        // Test for saving attributes.
-        testSaveAttributes_withFileName(fileName, expectedValue);
-    }
-
-    private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
-            throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-
-        // Test for reading from external data storage.
-        testExifInterfaceCommon(fileName, expectedValue);
-
-        // Since ExifInterface does not support for saving attributes for RAW files, do not test
-        // about writing back in here.
-    }
-
-    private void generateRandomExifTag(ByteBuffer buffer, int ifdType, Random random) {
-        ExifInterface.ExifTag[] tagGroup = ExifInterface.EXIF_TAGS[ifdType];
-        ExifInterface.ExifTag tag = tagGroup[random.nextInt(tagGroup.length)];
-        if (!randomlyCorrupted(random)) {
-            buffer.putShort((short) tag.number);
-        }
-        int dataFormat = random.nextInt(ExifInterface.IFD_FORMAT_NAMES.length);
-        if (!randomlyCorrupted(random)) {
-            buffer.putShort((short) dataFormat);
-        }
-        buffer.putInt(1);
-        int dataLength = ExifInterface.IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
-        if (dataLength > 4) {
-            buffer.putShort((short) random.nextInt(8096 - dataLength));
-            buffer.position(buffer.position() + 2);
-        } else {
-            buffer.position(buffer.position() + 4);
-        }
-    }
-
-    private boolean randomlyCorrupted(Random random) {
-        // Corrupts somewhere in a possibility of 1/500.
-        return random.nextInt(500) == 0;
-    }
-
-    private void closeQuietly(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    private int copy(InputStream in, OutputStream out) throws IOException {
-        int total = 0;
-        byte[] buffer = new byte[8192];
-        int c;
-        while ((c = in.read(buffer)) != -1) {
-            total += c;
-            out.write(buffer, 0, c);
-        }
-        return total;
-    }
-
-    private void assertLatLongValuesAreNotSet(ExifInterface exif) {
-        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
-        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
-        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
-        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
-    }
-
-    private ExifInterface createTestExifInterface() throws IOException {
-        File image = File.createTempFile(TEST_TEMP_FILE_NAME, ".jpg");
-        image.deleteOnExit();
-        return new ExifInterface(image.getAbsolutePath());
-    }
-}
diff --git a/fragment/Android.mk b/fragment/Android.mk
index efac954..6513542 100644
--- a/fragment/Android.mk
+++ b/fragment/Android.mk
@@ -32,11 +32,12 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
     android-support-core-ui \
-    android-support-core-utils \
-    android-support-annotations
+    android-support-core-utils
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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 303c192..d5371f5 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'
+    }
 }
 
 supportLibrary {
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/Android.mk b/graphics/drawable/Android.mk
index b1f0b38..6667dd9 100644
--- a/graphics/drawable/Android.mk
+++ b/graphics/drawable/Android.mk
@@ -26,9 +26,10 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, static/src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/static/res
 LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-compat \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -45,10 +46,11 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, animated/src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/animated/res
 LOCAL_MANIFEST_FILE := animated/AndroidManifest.xml
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
     android-support-vectordrawable \
-    android-support-annotations \
     android-support-core-ui
 LOCAL_AAPT_FLAGS := --no-version-vectors --add-javadoc-annotation doconly
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
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 43e8504..30827a9 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 d71161a..f0ac9f0 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..4e526c8
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt
@@ -0,0 +1,137 @@
+/*
+ * 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
+    }
+
+    override fun rewriteString(value: String): String {
+        // We don't build map from strings
+        return value
+    }
+
+    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..38ce393
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.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.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
+
+    fun rewriteString(value: String): String
+}
\ 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..71c4e9e
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.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.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 {
+        if (!context.isEligibleForRewrite(type)) {
+            return type
+        }
+
+        val result = typesMap.types[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 {
+        if (!context.isEligibleForRewrite(field.owner)) {
+            return field
+        }
+
+        val result = typesMap.fields[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
+    }
+
+    override fun rewriteString(value: String): String {
+        val type = JavaType.fromDotVersion(value)
+        if (!context.isEligibleForRewrite(type)) {
+            return value
+        }
+
+        val result = typesMap.types[type]
+        if (result != null) {
+            Log.i(TAG, "  map string: %s -> %s", type, result)
+            return result.toDotNotation()
+        }
+
+        // We do not treat string content mismatches as errors
+        return value
+    }
+}
+
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..aba4725
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt
@@ -0,0 +1,49 @@
+/*
+ * 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(private val remapper: CoreRemapper) : Remapper() {
+
+    override fun map(typeName: String): String {
+        return remapper.rewriteType(JavaType(typeName)).fullName
+    }
+
+    override fun mapValue(value: Any?): Any? {
+        val stringMaybe = value as? String
+        if (stringMaybe == null) {
+            return super.mapValue(value)
+        }
+
+        return remapper.rewriteString(stringMaybe)
+    }
+
+    fun mapField(ownerName: String, fieldName: String): JavaField {
+        return remapper.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..7b516ec
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt
@@ -0,0 +1,148 @@
+/*
+ * 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 defined via string notation to be processed by jetifyLibs task while also
+     * applying the given exclude rules.
+     *
+     *
+     * Example usage in Gradle:
+     * dependencies {
+     *   compile jetifier.processAndExclude('groupId:artifactId:1.0',
+     *     [group: 'some.package', module: 'moduleName'])
+     * }
+     */
+    fun processAndExclude(
+        dependencyNotation: String,
+        vararg excludes: Map<String, String>
+    ): FileCollection {
+        return processAndExclude(project.dependencies.create(dependencyNotation), *excludes)
+    }
+
+    /**
+     * 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 dependency to be processed by jetifyLibs task while also applying the given excludes
+     * rules.
+     */
+    fun processAndExclude(
+        dependency: Dependency,
+        vararg excludes: Map<String, String>
+    ): FileCollection {
+        val configuration = project.configurations.detachedConfiguration()
+        configuration.dependencies.add(dependency)
+        excludes.forEach { configuration.exclude(it) }
+        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..4c26406
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.Log
+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)
+
+        project.afterEvaluate({
+            val jetifyLibs = it.tasks.findByName(JetifyLibsTask.TASK_NAME)
+            val jetifyGlobal = it.tasks.findByName(JetifyGlobalTask.TASK_NAME)
+
+            if (jetifyLibs == null && jetifyGlobal == null) {
+                return@afterEvaluate
+            }
+
+            if (jetifyLibs != null && jetifyGlobal != null) {
+                jetifyGlobal.dependsOn(jetifyLibs)
+            }
+
+            val preBuildTask = it.tasks.findByName("preBuild")
+            if (preBuildTask == null) {
+                Log.e("TAG", "Failed to hook jetifier tasks. PreBuild task was not found.")
+                return@afterEvaluate
+            }
+
+            if (jetifyGlobal != null) {
+                preBuildTask.dependsOn(jetifyGlobal)
+            } else {
+                preBuildTask.dependsOn(jetifyLibs)
+            }
+        })
+    }
+}
\ 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..0ca725e
--- /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.4-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/leanback/Android.mk b/leanback/Android.mk
new file mode 100644
index 0000000..e39261f
--- /dev/null
+++ b/leanback/Android.mk
@@ -0,0 +1,49 @@
+# 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)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v17-leanback \
+#       android-support-v7-recyclerview \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
+LOCAL_MODULE := android-support-v17-leanback
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, common) \
+    $(call all-java-files-under, jbmr2) \
+    $(call all-java-files-under, kitkat) \
+    $(call all-java-files-under, api21) \
+    $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-recyclerview \
+    android-support-compat \
+    android-support-core-ui \
+    android-support-media-compat \
+    android-support-fragment
+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/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..05a91df
--- /dev/null
+++ b/leanback/build.gradle
@@ -0,0 +1,44 @@
+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 {
+    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
+    minSdkVersion = 17
+}
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..613198f
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -0,0 +1,3799 @@
+/*
+ * 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.Build;
+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);
+        int translatedAction = action;
+        boolean reverseFlowPrimary = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0;
+        if (Build.VERSION.SDK_INT >= 23) {
+            if (mOrientation == HORIZONTAL) {
+                if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                        .ACTION_SCROLL_LEFT.getId()) {
+                    translatedAction = reverseFlowPrimary
+                            ? AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD :
+                            AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
+                } else if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                        .ACTION_SCROLL_RIGHT.getId()) {
+                    translatedAction = reverseFlowPrimary
+                            ? AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD :
+                            AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
+                }
+            } else { // VERTICAL layout
+                if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP
+                        .getId()) {
+                    translatedAction = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
+                } else if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                        .ACTION_SCROLL_DOWN.getId()) {
+                    translatedAction = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
+                }
+            }
+        }
+        switch (translatedAction) {
+            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();
+        boolean reverseFlowPrimary = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0;
+        if (count > 1 && !isItemFullyVisible(0)) {
+            if (Build.VERSION.SDK_INT >= 23) {
+                if (mOrientation == HORIZONTAL) {
+                    info.addAction(reverseFlowPrimary
+                            ? AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                                    .ACTION_SCROLL_RIGHT :
+                            AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                                    .ACTION_SCROLL_LEFT);
+                } else {
+                    info.addAction(
+                            AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP);
+                }
+            } else {
+                info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+            }
+            info.setScrollable(true);
+        }
+        if (count > 1 && !isItemFullyVisible(count - 1)) {
+            if (Build.VERSION.SDK_INT >= 23) {
+                if (mOrientation == HORIZONTAL) {
+                    info.addAction(reverseFlowPrimary
+                            ? AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                                    .ACTION_SCROLL_LEFT :
+                            AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                                    .ACTION_SCROLL_RIGHT);
+                } else {
+                    info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat
+                                    .ACTION_SCROLL_DOWN);
+                }
+            } else {
+                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..4548494
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -0,0 +1,5993 @@
+/*
+ * 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 testAccessibilityRespondToLeftRight() 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.item_button_at_bottom);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        int width = mGridView.getWidth() - mGridView.getPaddingLeft()
+                - mGridView.getPaddingRight();
+        final int childWidth = width - mGridView.getHorizontalSpacing() - 500;
+        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[]{childWidth, childWidth});
+        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());
+        if (Build.VERSION.SDK_INT >= 23) {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_LEFT));
+        } else {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_BACKWARD));
+        }
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .AccessibilityActionCompat.ACTION_SCROLL_LEFT.getId(), null);
+                } else {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .ACTION_SCROLL_BACKWARD, null);
+                }
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, mGridView.getSelectedPosition());
+        setSelectedPosition(0);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        if (Build.VERSION.SDK_INT >= 23) {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_RIGHT));
+        } else {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_FORWARD));
+        }
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .AccessibilityActionCompat.ACTION_SCROLL_RIGHT.getId(), null);
+                } else {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .ACTION_SCROLL_FORWARD, null);
+                }
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testAccessibilityRespondToLeftRightRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear_rtl);
+        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.HORIZONTAL;
+        mNumRows = 1;
+
+        int width = mGridView.getWidth() - mGridView.getPaddingLeft()
+                - mGridView.getPaddingRight();
+        final int childWidth = width - mGridView.getHorizontalSpacing() - 500;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+                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[]{childWidth, childWidth});
+        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());
+        if (Build.VERSION.SDK_INT >= 23) {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_RIGHT));
+        } else {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_BACKWARD));
+        }
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .AccessibilityActionCompat.ACTION_SCROLL_RIGHT.getId(), null);
+                } else {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .ACTION_SCROLL_BACKWARD, null);
+                }
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, mGridView.getSelectedPosition());
+        setSelectedPosition(0);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        if (Build.VERSION.SDK_INT >= 23) {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_LEFT));
+        } else {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_FORWARD));
+        }
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .AccessibilityActionCompat.ACTION_SCROLL_LEFT.getId(), null);
+                } else {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .ACTION_SCROLL_FORWARD, null);
+                }
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testAccessibilityRespondToScrollUpAction() 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(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());
+        if (Build.VERSION.SDK_INT >= 23) {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_UP));
+        } else {
+            assertTrue("test sanity", info.removeAction(AccessibilityNodeInfoCompat
+                    .AccessibilityActionCompat.ACTION_SCROLL_BACKWARD));
+        }
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (Build.VERSION.SDK_INT >= 23) {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .AccessibilityActionCompat.ACTION_SCROLL_UP.getId(), null);
+                } else {
+                    delegateCompat.performAccessibilityAction(mGridView, AccessibilityNodeInfoCompat
+                            .ACTION_SCROLL_BACKWARD, null);
+                }
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, 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/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 8d8ba27..0000000
--- a/media-compat-test-client/build.gradle
+++ /dev/null
@@ -1,34 +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)
-}
-
-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 6b3063f..0000000
--- a/media-compat-test-service/build.gradle
+++ /dev/null
@@ -1,33 +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)
-}
-
-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/Android.mk b/media-compat/Android.mk
index 4a4cd02..8538752 100644
--- a/media-compat/Android.mk
+++ b/media-compat/Android.mk
@@ -36,9 +36,10 @@
     $(call all-java-files-under,java) \
     $(call all-Iaidl-files-under,java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-compat \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index 110ca84..ffa6e93 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/java/android/support/v4/media/session/PlaybackStateCompat.java b/media-compat/java/android/support/v4/media/session/PlaybackStateCompat.java
index d7634b0..3b06125 100644
--- a/media-compat/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/media-compat/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.support.annotation.IntDef;
+import android.support.annotation.LongDef;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.text.TextUtils;
@@ -45,7 +46,7 @@
      * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
-    @IntDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
+    @LongDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
             ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
             ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
             ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
@@ -58,7 +59,7 @@
      * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
-    @IntDef({ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS,
+    @LongDef({ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND, ACTION_SKIP_TO_PREVIOUS,
             ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_PLAY_PAUSE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface MediaKeyAction {}
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..9c4f879
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/build.gradle
@@ -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.
+ */
+
+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)
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
\ No newline at end of file
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..3cb3a49
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/build.gradle
@@ -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.
+ */
+
+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)
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
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/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/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/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 8848dbc..951a0e0 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/preference-leanback/Android.mk b/preference-leanback/Android.mk
new file mode 100644
index 0000000..e2ad1de
--- /dev/null
+++ b/preference-leanback/Android.mk
@@ -0,0 +1,51 @@
+# 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)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v17-preference-leanback \
+#       android-support-v17-leanback \
+#       android-support-v14-preference \
+#       android-support-v7-preference \
+#       android-support-v7-appcompat \
+#       android-support-v7-recyclerview \
+#       android-support-v4
+#
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
+LOCAL_MODULE := android-support-v17-preference-leanback
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under,api21) \
+    $(call all-java-files-under,src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v17-leanback \
+    android-support-v14-preference \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview \
+    android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
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/recommendation/Android.mk b/recommendation/Android.mk
index ea819e9..6249df7 100644
--- a/recommendation/Android.mk
+++ b/recommendation/Android.mk
@@ -27,9 +27,10 @@
 LOCAL_SDK_VERSION := current
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-v4 \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v4
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/recyclerview-selection/Android.mk b/recyclerview-selection/Android.mk
new file mode 100644
index 0000000..511806f
--- /dev/null
+++ b/recyclerview-selection/Android.mk
@@ -0,0 +1,31 @@
+# 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_JAVA_LIBRARIES := \
+    android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-recyclerview \
+    android-support-compat
+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/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
index f85105f..13f849e 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/widget/ExploreByTouchHelperActivity.java
@@ -16,7 +16,6 @@
 
 package com.example.android.supportv4.widget;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -26,7 +25,6 @@
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
@@ -115,7 +113,6 @@
             ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
         }
 
-        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
         @Override
         public boolean dispatchHoverEvent(MotionEvent event) {
             // Always attempt to dispatch hover events to accessibility first.
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/media/LocalPlayer.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/LocalPlayer.java
index 2d0cb56..03f3318 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/LocalPlayer.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/LocalPlayer.java
@@ -16,7 +16,6 @@
 
 package com.example.android.supportv7.media;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.Presentation;
 import android.content.Context;
@@ -27,6 +26,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.support.annotation.RequiresApi;
 import android.support.v7.media.MediaItemStatus;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
@@ -374,7 +374,6 @@
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
     private static final class ICSMediaPlayer {
         public static final void setSurface(MediaPlayer player, Surface surface) {
             player.setSurface(surface);
@@ -426,7 +425,7 @@
             mLayout.setVisibility(View.GONE);
         }
 
-        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
         @Override
         public void updatePresentation() {
             // Get the current route and its presentation display.
@@ -540,7 +539,7 @@
             }
         };
 
-        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
         private void releasePresentation() {
             // dismiss presentation display
             if (mPresentation != null) {
@@ -551,7 +550,7 @@
         }
 
         // Presentation
-        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
         private final class DemoPresentation extends Presentation {
             private SurfaceView mPresentationSurfaceView;
 
@@ -568,7 +567,7 @@
                 setContentView(R.layout.sample_media_router_presentation);
 
                 // Set up the surface view.
-                mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
+                mPresentationSurfaceView = findViewById(R.id.surface_view);
                 SurfaceHolder holder = mPresentationSurfaceView.getHolder();
                 holder.addCallback(SurfaceViewPlayer.this);
                 Log.i(TAG, "Presentation created");
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/OverlayDisplayWindow.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/OverlayDisplayWindow.java
index d6a82ef..1fa05d3 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/OverlayDisplayWindow.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/OverlayDisplayWindow.java
@@ -16,12 +16,12 @@
 
 package com.example.android.supportv7.media;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.SurfaceTexture;
 import android.hardware.display.DisplayManager;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
@@ -186,7 +186,7 @@
     /**
      * Implementation for API version 17+.
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
         // When true, disables support for moving and resizing the overlay.
         // The window is made non-touchable, which makes it possible to
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/Player.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/Player.java
index 380e945..82de473 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/Player.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/media/Player.java
@@ -16,10 +16,10 @@
 
 package com.example.android.supportv7.media;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Build;
+import android.support.annotation.RequiresApi;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -77,7 +77,7 @@
     /**
      * presentation display
      */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
     public void updatePresentation() {}
 
     public void setCallback(Callback callback) {
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
index cf2fac2..1f9bb7d 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/AnimatedRecyclerView.java
@@ -18,10 +18,8 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.util.ArrayMap;
 import android.support.v7.widget.DefaultItemAnimator;
@@ -207,14 +205,12 @@
             public boolean animateChange(RecyclerView.ViewHolder oldHolder,
                     RecyclerView.ViewHolder newHolder, ItemHolderInfo preInfo,
                     ItemHolderInfo postInfo) {
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1
-                        || oldHolder != newHolder) {
+                if (oldHolder != newHolder) {
                     return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
                 }
                 return animateChangeApiHoneycombMr1(oldHolder, newHolder, preInfo, postInfo);
             }
 
-            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
             private boolean animateChangeApiHoneycombMr1(RecyclerView.ViewHolder oldHolder,
                     RecyclerView.ViewHolder newHolder,
                     ItemHolderInfo preInfo, ItemHolderInfo postInfo) {
@@ -260,8 +256,6 @@
         };
     }
 
-
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
     abstract private static class ItemChangeAnimator implements
             ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         CharSequence mFinalText;
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/java/com/example/android/supportv7/widget/touch/SwipeToDismissActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/SwipeToDismissActivity.java
index 14a7495..6be0398 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/SwipeToDismissActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/touch/SwipeToDismissActivity.java
@@ -16,9 +16,7 @@
 
 package com.example.android.supportv7.widget.touch;
 
-import android.annotation.TargetApi;
 import android.graphics.Canvas;
-import android.os.Build;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
@@ -73,24 +71,20 @@
                     }
                 }
         };
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            ConfigToggle[] copy = new ConfigToggle[configToggles.length + 1];
-            System.arraycopy(configToggles, 0, copy, 0, configToggles.length);
-            copy[copy.length - 1] = new ConfigToggle(this, R.string.custom_swipe_enabled) {
-                @Override
-                public boolean isChecked() {
-                    return mCustomSwipeEnabled;
-                }
+        ConfigToggle[] copy = new ConfigToggle[configToggles.length + 1];
+        System.arraycopy(configToggles, 0, copy, 0, configToggles.length);
+        copy[copy.length - 1] = new ConfigToggle(this, R.string.custom_swipe_enabled) {
+            @Override
+            public boolean isChecked() {
+                return mCustomSwipeEnabled;
+            }
 
-                @Override
-                public void onChange(boolean newValue) {
-                    mCustomSwipeEnabled = newValue;
-                }
-            };
-            return copy;
-        } else {
-            return configToggles;
-        }
+            @Override
+            public void onChange(boolean newValue) {
+                mCustomSwipeEnabled = newValue;
+            }
+        };
+        return copy;
     }
 
     @Override
@@ -109,7 +103,6 @@
     }
 
     @Override
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
         ItemTouchViewHolder touchVH = (ItemTouchViewHolder) viewHolder;
         if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
@@ -125,7 +118,6 @@
     }
 
     @Override
-    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     public boolean onChildDraw(Canvas c, RecyclerView recyclerView,
             RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState,
             boolean isCurrentlyActive) {
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/samples/SupportLeanbackShowcase/app/build.gradle b/samples/SupportLeanbackShowcase/app/build.gradle
deleted file mode 100644
index b2ff15b..0000000
--- a/samples/SupportLeanbackShowcase/app/build.gradle
+++ /dev/null
@@ -1,36 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
-    compileSdkVersion 24
-    buildToolsVersion "24.0.2"
-
-    defaultConfig {
-        applicationId "android.support.v17.leanback.supportleanbackshowcase"
-        minSdkVersion 17
-        targetSdkVersion 24
-        versionCode 1
-        versionName "1.0"
-        multiDexEnabled true
-    }
-    buildTypes {
-        release {
-            minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-        }
-    }
-}
-
-dependencies {
-    compile fileTree(dir: 'libs', include: ['*.jar'])
-    compile "com.android.support:recyclerview-v7:${supportLibVersion}"
-    compile "com.android.support:leanback-v17:${supportLibVersion}"
-    compile "com.android.support:appcompat-v7:${supportLibVersion}"
-
-    compile "com.android.support:preference-v7:${supportLibVersion}"
-    compile "com.android.support:preference-leanback-v17:${supportLibVersion}"
-    compile "com.android.support:preference-v14:${supportLibVersion}"
-
-    compile "com.google.code.gson:gson:1.7.2"
-    compile "com.squareup.picasso:picasso:2.5.2"
-    compile "com.android.support:palette-v7:${supportLibVersion}"
-}
diff --git a/samples/SupportLeanbackShowcase/app/proguard-rules.pro b/samples/SupportLeanbackShowcase/app/proguard-rules.pro
deleted file mode 100644
index cf489a5..0000000
--- a/samples/SupportLeanbackShowcase/app/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/hahnr/Library/Android/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
diff --git a/samples/SupportLeanbackShowcase/app/src/androidTest/java/android/support/v17/leanback/supportleanbackshowcase/ApplicationTest.java b/samples/SupportLeanbackShowcase/app/src/androidTest/java/android/support/v17/leanback/supportleanbackshowcase/ApplicationTest.java
deleted file mode 100644
index 4d13a4b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/androidTest/java/android/support/v17/leanback/supportleanbackshowcase/ApplicationTest.java
+++ /dev/null
@@ -1,27 +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.supportleanbackshowcase;
-
-import android.app.Application;
-import android.test.ApplicationTestCase;
-
-/**
- * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
- */
-public class ApplicationTest extends ApplicationTestCase<Application> {
-    public ApplicationTest() {
-        super(Application.class);
-    }
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml b/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 4e71c40..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<manifest
-    package="android.support.v17.leanback.supportleanbackshowcase"
-    xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="23" />
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false"/>
-    <uses-feature
-        android:name="android.software.leanback"
-        android:required="true"/>
-
-    <application
-        android:allowBackup="true"
-        android:icon="@mipmap/app_banner_sample_app"
-        android:label="@string/app_name"
-        android:supportsRtl="true"
-        android:largeHeap="true"
-        android:theme="@style/Theme.Example.LeanbackLauncher">
-        <activity
-            android:name=".app.MainActivity"
-            android:exported="true"
-            android:icon="@mipmap/app_banner_sample_app"
-            android:label="@string/app_name"
-            android:logo="@mipmap/app_banner_sample_app"
-            android:screenOrientation="landscape">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity
-                android:name=".app.page.PageAndListRowActivity"
-                android:exported="true"
-                android:theme="@style/Theme.Example.Leanback.CustomTitle"/>
-        <activity
-            android:name=".app.wizard.WizardExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackWizard"></activity>
-        <activity
-            android:name=".app.settings.SettingsExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackPreferences"></activity>
-        <activity
-            android:name=".app.dialog.DialogExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackDialog"></activity>
-        <activity
-            android:name=".app.details.DetailViewExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackDetails"></activity>
-        <activity
-            android:name=".app.cards.CardExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackBrowse"></activity>
-        <activity
-            android:name=".app.grid.GridExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackVerticalGrid"></activity>
-        <activity
-            android:name=".app.media.VideoExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.Leanback"></activity>
-        <activity
-            android:name=".app.media.MusicExampleActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Example.LeanbackMusic"></activity>
-    </application>
-
-</manifest>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainActivity.java
deleted file mode 100644
index e84c8cc..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainActivity.java
+++ /dev/null
@@ -1,39 +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.supportleanbackshowcase.app;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/*
- * MainActivity class that loads MainFragment
- */
-public class MainActivity extends Activity {
-
-    /**
-     * Called when the activity is first created.
-     */
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-        if (savedInstanceState == null) {
-            Fragment fragment = new MainFragment();
-            getFragmentManager().beginTransaction().replace(R.id.fragmentContainer, fragment)
-                                .commit();
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainFragment.java
deleted file mode 100644
index 6918539..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/MainFragment.java
+++ /dev/null
@@ -1,186 +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.supportleanbackshowcase.app;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.app.cards.CardExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.details.DetailViewExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.dialog.DialogExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.grid.GridExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.MusicExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.VideoExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.page.PageAndListRowActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.settings.SettingsExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.wizard.WizardExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.supportleanbackshowcase.models.Movie;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-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.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.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v4.app.ActivityOptionsCompat;
-
-import com.google.gson.Gson;
-
-
-public class MainFragment extends BrowseFragment {
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        setupUIElements();
-        setupRowAdapter();
-        setupEventListeners();
-    }
-
-    private void setupRowAdapter() {
-        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-        createRows();
-        setAdapter(mRowsAdapter);
-    }
-
-    private void createRows() {
-        String json = Utils
-                .inputStreamToString(getResources().openRawResource(R.raw.launcher_cards));
-        CardRow[] rows = new Gson().fromJson(json, CardRow[].class);
-        for (CardRow row : rows) {
-            mRowsAdapter.add(createCardRow(row));
-        }
-    }
-
-    private ListRow createCardRow(CardRow cardRow) {
-        PresenterSelector presenterSelector = new CardPresenterSelector(getActivity());
-        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenterSelector);
-        for (Card card : cardRow.getCards()) {
-            listRowAdapter.add(card);
-        }
-        return new ListRow(listRowAdapter);
-    }
-
-    private void setupUIElements() {
-        setTitle(getString(R.string.browse_title));
-        setBadgeDrawable(getResources().getDrawable(R.drawable.title_android_tv, null));
-        setHeadersState(HEADERS_DISABLED);
-        setHeadersTransitionOnBackEnabled(false);
-        setBrandColor(getResources().getColor(R.color.fastlane_background));
-    }
-
-    private void setupEventListeners() {
-        setOnItemViewClickedListener(new ItemViewClickedListener());
-        setOnItemViewSelectedListener(new ItemViewSelectedListener());
-    }
-
-    private final class ItemViewClickedListener implements OnItemViewClickedListener {
-
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
-            Intent intent = null;
-            Card card = (Card) item;
-            int id = card.getId();
-            switch (id) {
-                case 0: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            CardExampleActivity.class);
-                    break;
-                }
-                case 1:
-                    intent = new Intent(getActivity().getBaseContext(),
-                            PageAndListRowActivity.class);
-                    break;
-                case 2: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            GridExampleActivity.class);
-                    break;
-                }
-                case 3: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            DetailViewExampleActivity.class);
-                    break;
-                }
-                case 4: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            VideoExampleActivity.class);
-                    break;
-                }
-                case 5: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            MusicExampleActivity.class);
-                    break;
-                }
-                case 6: {
-                    // Let's create a new Wizard for a given Movie. The movie can come from any sort
-                    // of data source. To simplify this example we decode it from a JSON source
-                    // which might be loaded from a server in a real world example.
-                    intent = new Intent(getActivity().getBaseContext(),
-                            WizardExampleActivity.class);
-
-                    // Prepare extras which contains the Movie and will be passed to the Activity
-                    // which is started through the Intent/.
-                    Bundle extras = new Bundle();
-                    String json = Utils.inputStreamToString(
-                            getResources().openRawResource(R.raw.wizard_example));
-                    Movie movie = new Gson().fromJson(json, Movie.class);
-                    extras.putSerializable("movie", movie);
-                    intent.putExtras(extras);
-
-                    // Finally, start the wizard Activity.
-                    break;
-                }
-                case 7: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            SettingsExampleActivity.class);
-                    startActivity(intent);
-                    return;
-                }
-                case 8: {
-                    intent = new Intent(getActivity().getBaseContext(),
-                            DialogExampleActivity.class);
-                    break;
-                }
-                default:
-                    break;
-            }
-            if (intent != null) {
-                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity())
-                        .toBundle();
-                startActivity(intent, bundle);
-            }
-        }
-    }
-
-    private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
-
-        @Override
-        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleActivity.java
deleted file mode 100644
index ecb0c7a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleActivity.java
+++ /dev/null
@@ -1,34 +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.supportleanbackshowcase.app.cards;
-
-import android.app.Activity;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.app.dialog.DialogExampleFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * TODO: Javadoc
- */
-public class CardExampleActivity extends Activity {
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_cards_example);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleFragment.java
deleted file mode 100644
index ab683d0..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/cards/CardExampleFragment.java
+++ /dev/null
@@ -1,136 +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.supportleanbackshowcase.app.cards;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.app.details.DetailViewExampleActivity;
-import android.support.v17.leanback.supportleanbackshowcase.app.details.DetailViewExampleFragment;
-import android.support.v17.leanback.supportleanbackshowcase.app.details.ShadowRowPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.CardListRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.DividerRow;
-import android.support.v17.leanback.widget.SectionRow;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-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.SearchOrbView;
-import android.support.v4.app.ActivityOptionsCompat;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import com.google.gson.Gson;
-
-/**
- * This fragment will be shown when the "Card Examples" card is selected at the home menu. It will
- * display multiple card types.
- */
-public class CardExampleFragment extends BrowseFragment {
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    @Override public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        setupUi();
-        setupRowAdapter();
-    }
-
-    private void setupUi() {
-        setHeadersState(HEADERS_ENABLED);
-        setHeadersTransitionOnBackEnabled(true);
-        setTitle(getString(R.string.card_examples_title));
-        setOnSearchClickedListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Toast.makeText(getActivity(), getString(R.string.implement_search),
-                        Toast.LENGTH_LONG).show();
-            }
-        });
-        setOnItemViewClickedListener(new OnItemViewClickedListener() {
-
-            @Override
-            public void onItemClicked(Presenter.ViewHolder viewHolder, Object item, RowPresenter.ViewHolder viewHolder1, Row row) {
-                if (!(item instanceof Card)) return;
-                if (!(viewHolder.view instanceof ImageCardView)) return;
-
-                ImageView imageView = ((ImageCardView) viewHolder.view).getMainImageView();
-                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
-                        imageView, DetailViewExampleFragment.TRANSITION_NAME).toBundle();
-                Intent intent = new Intent(getActivity().getBaseContext(),
-                        DetailViewExampleActivity.class);
-                Card card = (Card) item;
-                int imageResId = card.getLocalImageResourceId(getContext());
-                intent.putExtra(DetailViewExampleFragment.EXTRA_CARD, imageResId);
-                startActivity(intent, bundle);
-            }
-
-        });
-
-        prepareEntranceTransition();
-    }
-
-    private void setupRowAdapter() {
-        mRowsAdapter = new ArrayObjectAdapter(new ShadowRowPresenterSelector());
-        setAdapter(mRowsAdapter);
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                createRows();
-                startEntranceTransition();
-            }
-        }, 500);
-    }
-
-    private void createRows() {
-        String json = Utils
-                .inputStreamToString(getResources().openRawResource(R.raw.cards_example));
-        CardRow[] rows = new Gson().fromJson(json, CardRow[].class);
-        for (CardRow row : rows) {
-            mRowsAdapter.add(createCardRow(row));
-        }
-    }
-
-    private Row createCardRow(final CardRow cardRow) {
-        switch (cardRow.getType()) {
-            case CardRow.TYPE_SECTION_HEADER:
-                return new SectionRow(new HeaderItem(cardRow.getTitle()));
-            case CardRow.TYPE_DIVIDER:
-                return new DividerRow();
-            case CardRow.TYPE_DEFAULT:
-            default:
-                // Build main row using the ImageCardViewPresenter.
-                PresenterSelector presenterSelector = new CardPresenterSelector(getActivity());
-                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenterSelector);
-                for (Card card : cardRow.getCards()) {
-                    listRowAdapter.add(card);
-                }
-                return new CardListRow(new HeaderItem(cardRow.getTitle()), listRowAdapter, cardRow);
-        }
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleActivity.java
deleted file mode 100644
index 6b9c143..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleActivity.java
+++ /dev/null
@@ -1,32 +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.supportleanbackshowcase.app.details;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * Contains a {@link DetailsFragment} in order to display more details for a given card.
- */
-public class DetailViewExampleActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_detail_example);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleFragment.java
deleted file mode 100644
index eed800f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailViewExampleFragment.java
+++ /dev/null
@@ -1,179 +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.supportleanbackshowcase.app.details;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.supportleanbackshowcase.models.DetailedCard;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.utils.CardListRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-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.FullWidthDetailsOverviewSharedElementHelper;
-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.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import com.google.gson.Gson;
-
-/**
- * Displays a card with more details using a {@link DetailsFragment}.
- */
-public class DetailViewExampleFragment extends DetailsFragment implements OnItemViewClickedListener,
-        OnItemViewSelectedListener {
-
-    public static final String TRANSITION_NAME = "t_for_transition";
-    public static final String EXTRA_CARD = "card";
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setupUi();
-        setupEventListeners();
-    }
-
-    private void setupUi() {
-        // Load the card we want to display from a JSON resource. This JSON data could come from
-        // anywhere in a real world app, e.g. a server.
-        String json = Utils
-                .inputStreamToString(getResources().openRawResource(R.raw.detail_example));
-        DetailedCard data = new Gson().fromJson(json, DetailedCard.class);
-
-        // Setup fragment
-        setTitle(getString(R.string.detail_view_title));
-
-        FullWidthDetailsOverviewRowPresenter rowPresenter = new FullWidthDetailsOverviewRowPresenter(
-                new DetailsDescriptionPresenter(getActivity())) {
-
-            @Override
-            protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
-                // Customize Actionbar and Content by using custom colors.
-                RowPresenter.ViewHolder viewHolder = super.createRowViewHolder(parent);
-
-                View actionsView = viewHolder.view.
-                        findViewById(R.id.details_overview_actions_background);
-                actionsView.setBackgroundColor(getActivity().getResources().
-                        getColor(R.color.detail_view_actionbar_background, null));
-
-                View detailsView = viewHolder.view.findViewById(R.id.details_frame);
-                detailsView.setBackgroundColor(
-                        getResources().getColor(R.color.detail_view_background, null));
-                return viewHolder;
-            }
-        };
-
-        FullWidthDetailsOverviewSharedElementHelper mHelper = new FullWidthDetailsOverviewSharedElementHelper();
-        mHelper.setSharedElementEnterTransition(getActivity(), TRANSITION_NAME);
-        rowPresenter.setListener(mHelper);
-        rowPresenter.setParticipatingEntranceTransition(false);
-        prepareEntranceTransition();
-
-        ListRowPresenter shadowDisabledRowPresenter = new ListRowPresenter();
-        shadowDisabledRowPresenter.setShadowEnabled(false);
-
-        // Setup PresenterSelector to distinguish between the different rows.
-        ClassPresenterSelector rowPresenterSelector = new ClassPresenterSelector();
-        rowPresenterSelector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
-        rowPresenterSelector.addClassPresenter(CardListRow.class, shadowDisabledRowPresenter);
-        rowPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
-        mRowsAdapter = new ArrayObjectAdapter(rowPresenterSelector);
-
-        // Setup action and detail row.
-        DetailsOverviewRow detailsOverview = new DetailsOverviewRow(data);
-        int imageResId = data.getLocalImageResourceId(getActivity());
-
-        Bundle extras = getActivity().getIntent().getExtras();
-        if (extras != null && extras.containsKey(EXTRA_CARD)) {
-            imageResId = extras.getInt(EXTRA_CARD, imageResId);
-        }
-        detailsOverview.setImageDrawable(getResources().getDrawable(imageResId, null));
-        ArrayObjectAdapter actionAdapter = new ArrayObjectAdapter();
-        actionAdapter.add(new Action(1, getString(R.string.action_buy) + data.getPrice()));
-        actionAdapter.add(new Action(2, getString(R.string.action_wishlist)));
-        actionAdapter.add(new Action(3, getString(R.string.action_related)));
-        detailsOverview.setActionsAdapter(actionAdapter);
-        mRowsAdapter.add(detailsOverview);
-
-        // Setup related row.
-        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
-                new CardPresenterSelector(getActivity()));
-        for (Card characterCard : data.getCharacters()) listRowAdapter.add(characterCard);
-        HeaderItem header = new HeaderItem(0, getString(R.string.header_related));
-        mRowsAdapter.add(new CardListRow(header, listRowAdapter, null));
-
-        // Setup recommended row.
-        listRowAdapter = new ArrayObjectAdapter(new CardPresenterSelector(getActivity()));
-        for (Card card : data.getRecommended()) listRowAdapter.add(card);
-        header = new HeaderItem(1, getString(R.string.header_recommended));
-        mRowsAdapter.add(new ListRow(header, listRowAdapter));
-
-        setAdapter(mRowsAdapter);
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                startEntranceTransition();
-            }
-        }, 500);
-    }
-
-    private void setupEventListeners() {
-        setOnItemViewSelectedListener(this);
-        setOnItemViewClickedListener(this);
-    }
-
-    @Override
-    public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                              RowPresenter.ViewHolder rowViewHolder, Row row) {
-        if (!(item instanceof Action)) return;
-        Action action = (Action) item;
-        if (action.getId() == 3) {
-            setSelectedPosition(1);
-        } else {
-            Toast.makeText(getActivity(), getString(R.string.action_cicked), Toast.LENGTH_LONG)
-                    .show();
-        }
-    }
-
-    @Override
-    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                               RowPresenter.ViewHolder rowViewHolder, Row row) {
-        if (mRowsAdapter.indexOf(row) > 0) {
-            int backgroundColor = getResources().getColor(R.color.detail_view_related_background,
-                    null);
-            getView().setBackgroundColor(backgroundColor);
-        } else {
-            getView().setBackground(null);
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailsDescriptionPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailsDescriptionPresenter.java
deleted file mode 100644
index b8488a1..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/DetailsDescriptionPresenter.java
+++ /dev/null
@@ -1,62 +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.supportleanbackshowcase.app.details;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.DetailedCard;
-import android.support.v17.leanback.supportleanbackshowcase.utils.ResourceCache;
-import android.support.v17.leanback.widget.Presenter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-/**
- * This presenter is used to render a {@link DetailedCard} in the {@link
- * DetailViewExampleFragment}.
- */
-public class DetailsDescriptionPresenter extends Presenter {
-
-    private ResourceCache mResourceCache = new ResourceCache();
-    private Context mContext;
-
-    public DetailsDescriptionPresenter(Context context) {
-        mContext = context;
-    }
-
-    @Override public ViewHolder onCreateViewHolder(ViewGroup parent) {
-        View view = LayoutInflater.from(mContext).inflate(R.layout.detail_view_content, null);
-        return new ViewHolder(view);
-    }
-
-    @Override public void onBindViewHolder(ViewHolder viewHolder, Object item) {
-        TextView primaryText = mResourceCache.getViewById(viewHolder.view, R.id.primary_text);
-        TextView sndText1 = mResourceCache.getViewById(viewHolder.view, R.id.secondary_text_first);
-        TextView sndText2 = mResourceCache.getViewById(viewHolder.view, R.id.secondary_text_second);
-        TextView extraText = mResourceCache.getViewById(viewHolder.view, R.id.extra_text);
-
-        DetailedCard card = (DetailedCard) item;
-        primaryText.setText(card.getTitle());
-        sndText1.setText(card.getDescription());
-        sndText2.setText(card.getYear() + "");
-        extraText.setText(card.getText());
-    }
-
-    @Override public void onUnbindViewHolder(ViewHolder viewHolder) {
-        // Nothing to do here.
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/ShadowRowPresenterSelector.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/ShadowRowPresenterSelector.java
deleted file mode 100644
index 7df3bf8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/details/ShadowRowPresenterSelector.java
+++ /dev/null
@@ -1,52 +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.supportleanbackshowcase.app.details;
-
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.CardListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-
-/**
- * This {@link PresenterSelector} will return a {@link ListRowPresenter} which has shadow support
- * enabled or not depending on {@link CardRow#useShadow()} for a given row.
- */
-public class ShadowRowPresenterSelector extends PresenterSelector {
-
-    private ListRowPresenter mShadowEnabledRowPresenter = new ListRowPresenter();
-    private ListRowPresenter mShadowDisabledRowPresenter = new ListRowPresenter();
-
-    public ShadowRowPresenterSelector() {
-        mShadowDisabledRowPresenter.setShadowEnabled(false);
-    }
-
-    @Override public Presenter getPresenter(Object item) {
-        if (!(item instanceof CardListRow)) return mShadowDisabledRowPresenter;
-        CardListRow listRow = (CardListRow) item;
-        CardRow row = listRow.getCardRow();
-        if (row.useShadow()) return mShadowEnabledRowPresenter;
-        return mShadowDisabledRowPresenter;
-    }
-
-    @Override
-    public Presenter[] getPresenters() {
-        return new Presenter [] {
-                mShadowDisabledRowPresenter,
-                mShadowEnabledRowPresenter
-        };
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleActivity.java
deleted file mode 100644
index 0c71739..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleActivity.java
+++ /dev/null
@@ -1,37 +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.supportleanbackshowcase.app.dialog;
-
-import android.app.Activity;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-
-/**
- * TODO: Javadoc
- */
-public class DialogExampleActivity extends Activity {
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().setBackgroundDrawable(new ColorDrawable(Color.parseColor("#21272A")));
-
-        if (savedInstanceState == null) {
-            GuidedStepFragment fragment = new DialogExampleFragment();
-            GuidedStepFragment.addAsRoot(this, fragment, android.R.id.content);
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleFragment.java
deleted file mode 100644
index 80b22a9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/dialog/DialogExampleFragment.java
+++ /dev/null
@@ -1,67 +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.supportleanbackshowcase.app.dialog;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.widget.Toast;
-
-import java.util.List;
-
-/**
- * TODO: Javadoc
- */
-public class DialogExampleFragment extends GuidedStepFragment {
-
-    private static final int ACTION_ID_POSITIVE = 1;
-    private static final int ACTION_ID_NEGATIVE = ACTION_ID_POSITIVE + 1;
-
-    @NonNull
-    @Override
-    public Guidance onCreateGuidance(Bundle savedInstanceState) {
-        Guidance guidance = new Guidance(getString(R.string.dialog_example_title),
-                getString(R.string.dialog_example_description),
-                "", null);
-        return guidance;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        GuidedAction action = new GuidedAction.Builder()
-                .id(ACTION_ID_POSITIVE)
-                .title(getString(R.string.dialog_example_button_positive)).build();
-        actions.add(action);
-        action = new GuidedAction.Builder()
-                .id(ACTION_ID_NEGATIVE)
-                .title(getString(R.string.dialog_example_button_negative)).build();
-        actions.add(action);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (ACTION_ID_POSITIVE == action.getId()) {
-            Toast.makeText(getActivity(), R.string.dialog_example_button_toast_positive_clicked,
-                    Toast.LENGTH_SHORT).show();
-        } else {
-            Toast.makeText(getActivity(), R.string.dialog_example_button_toast_negative_clicked,
-                    Toast.LENGTH_SHORT).show();
-        }
-        getActivity().finish();
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleActivity.java
deleted file mode 100644
index e2d0887..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleActivity.java
+++ /dev/null
@@ -1,30 +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.supportleanbackshowcase.app.grid;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * TODO: Javadoc
- */
-public class GridExampleActivity extends Activity {
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_grid_example);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleFragment.java
deleted file mode 100644
index 6640b51..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/grid/GridExampleFragment.java
+++ /dev/null
@@ -1,72 +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.supportleanbackshowcase.app.grid;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.VerticalGridFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.FocusHighlight;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-
-import com.google.gson.Gson;
-
-/**
- * An example how to use leanback's {@link VerticalGridFragment}.
- */
-public class GridExampleFragment extends VerticalGridFragment {
-
-    private static final int COLUMNS = 4;
-    private static final int ZOOM_FACTOR = FocusHighlight.ZOOM_FACTOR_MEDIUM;
-
-    private ArrayObjectAdapter mAdapter;
-
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setTitle(getString(R.string.grid_example_title));
-        setupRowAdapter();
-    }
-
-    private void setupRowAdapter() {
-        VerticalGridPresenter gridPresenter = new VerticalGridPresenter(ZOOM_FACTOR);
-        gridPresenter.setNumberOfColumns(COLUMNS);
-        setGridPresenter(gridPresenter);
-
-        PresenterSelector cardPresenterSelector = new CardPresenterSelector(getActivity());
-        mAdapter = new ArrayObjectAdapter(cardPresenterSelector);
-        setAdapter(mAdapter);
-
-        prepareEntranceTransition();
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                createRows();
-                startEntranceTransition();
-            }
-        }, 1000);
-    }
-
-    private void createRows() {
-        String json = Utils.inputStreamToString(getResources()
-                .openRawResource(R.raw.grid_example));
-        CardRow row = new Gson().fromJson(json, CardRow.class);
-        mAdapter.addAll(0, row.getCards());
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
deleted file mode 100644
index f64899e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
+++ /dev/null
@@ -1,456 +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.supportleanbackshowcase.app.media;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.Handler;
-import android.support.v17.leanback.app.PlaybackControlGlue;
-import android.support.v17.leanback.app.PlaybackOverlayFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
-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.util.Log;
-import android.view.KeyEvent;
-import android.view.SurfaceHolder;
-import android.view.View;
-
-import java.io.IOException;
-
-/**
- * This glue extends the {@link PlaybackControlGlue} with a {@link MediaPlayer} synchronization. It
- * supports 7 actions: <ul> <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction}</li>
- * <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}</li> <li>{@link
- * android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction}</li> <li>{@link
- * android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction}</li> <li>{@link
- * android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction}</li> <li>{@link
- * android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction}</li> <li>{@link
- * android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction}</li> </ul>
- * <p/>
- */
-public abstract class MediaPlayerGlue extends PlaybackControlGlue implements
-        OnItemViewSelectedListener {
-
-    public static final int FAST_FORWARD_REWIND_STEP = 10 * 1000; // in milliseconds
-    public static final int FAST_FORWARD_REWIND_REPEAT_DELAY = 200; // in milliseconds
-    private static final String TAG = "MediaPlayerGlue";
-    protected final PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
-    protected final PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
-    private final Context mContext;
-    private final MediaPlayer mPlayer = new MediaPlayer();
-    private final PlaybackControlsRow.RepeatAction mRepeatAction;
-    private final PlaybackControlsRow.ShuffleAction mShuffleAction;
-    private PlaybackControlsRow mControlsRow;
-    private Runnable mRunnable;
-    private Handler mHandler = new Handler();
-    private boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized
-    private OnMediaFileFinishedPlayingListener mMediaFileFinishedPlayingListener;
-    private Action mSelectedAction; // the action which is currently selected by the user
-    private long mLastKeyDownEvent = 0L; // timestamp when the last DPAD_CENTER KEY_DOWN occurred
-    private MetaData mMetaData;
-    private Uri mMediaSourceUri = null;
-    private String mMediaSourcePath = null;
-
-    public MediaPlayerGlue(Context context, PlaybackOverlayFragment fragment) {
-        super(context, fragment, new int[]{1});
-        mContext = context;
-
-        // Instantiate secondary actions
-        mShuffleAction = new PlaybackControlsRow.ShuffleAction(mContext);
-        mRepeatAction = new PlaybackControlsRow.RepeatAction(mContext);
-        mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(mContext);
-        mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(mContext);
-        mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-        mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-
-        // Register selected listener such that we know what action the user currently has focused.
-        fragment.setOnItemViewSelectedListener(this);
-    }
-
-    /**
-     * Will reset the {@link MediaPlayer} and the glue such that a new file can be played. You are
-     * not required to call this method before playing the first file. However you have to call it
-     * before playing a second one.
-     */
-    void reset() {
-        mInitialized = false;
-        mPlayer.reset();
-    }
-
-    public void setOnMediaFileFinishedPlayingListener(OnMediaFileFinishedPlayingListener listener) {
-        mMediaFileFinishedPlayingListener = listener;
-    }
-
-    /**
-     * Override this method in case you need to add different secondary actions.
-     *
-     * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
-     */
-    protected void addSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
-        secondaryActionsAdapter.add(mShuffleAction);
-        secondaryActionsAdapter.add(mRepeatAction);
-        secondaryActionsAdapter.add(mThumbsDownAction);
-        secondaryActionsAdapter.add(mThumbsUpAction);
-    }
-
-    /**
-     * @see MediaPlayer#setDisplay(SurfaceHolder)
-     */
-    public void setDisplay(SurfaceHolder surfaceHolder) {
-        mPlayer.setDisplay(surfaceHolder);
-    }
-
-    /**
-     * Use this method to setup the {@link PlaybackControlsRowPresenter}. It'll be called
-     * <u>after</u> the {@link PlaybackControlsRowPresenter} has been created and the primary and
-     * secondary actions have been added.
-     *
-     * @param presenter The PlaybackControlsRowPresenter used to display the controls.
-     */
-    public void setupControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
-        // TODO: hahnr@ move into resources
-        presenter.setProgressColor(getContext().getResources().getColor(
-                R.color.player_progress_color));
-        presenter.setBackgroundColor(getContext().getResources().getColor(
-                R.color.player_background_color));
-    }
-
-    @Override public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
-        PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
-        mControlsRow = getControlsRow();
-
-        // Add secondary actions and change the control row color.
-        ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
-                new ControlButtonPresenterSelector());
-        mControlsRow.setSecondaryActionsAdapter(secondaryActions);
-        addSecondaryActions(secondaryActions);
-        setupControlsRowPresenter(presenter);
-        return presenter;
-    }
-
-    @Override public void enableProgressUpdating(final boolean enabled) {
-        if (!enabled) {
-            if (mRunnable != null) mHandler.removeCallbacks(mRunnable);
-            return;
-        }
-        mRunnable = new Runnable() {
-            @Override public void run() {
-                updateProgress();
-                Log.d(TAG, "enableProgressUpdating(boolean)");
-                mHandler.postDelayed(this, getUpdatePeriod());
-            }
-        };
-        mHandler.postDelayed(mRunnable, getUpdatePeriod());
-    }
-
-    @Override public void onActionClicked(Action action) {
-        // If either 'Shuffle' or 'Repeat' has been clicked we need to make sure the acitons index
-        // is incremented and the UI updated such that we can display the new state.
-        super.onActionClicked(action);
-        if (action instanceof PlaybackControlsRow.ShuffleAction) {
-            mShuffleAction.nextIndex();
-        } else if (action instanceof PlaybackControlsRow.RepeatAction) {
-            mRepeatAction.nextIndex();
-        } else if (action instanceof PlaybackControlsRow.ThumbsUpAction) {
-            if (mThumbsUpAction.getIndex() == PlaybackControlsRow.ThumbsAction.SOLID) {
-                mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-            } else {
-                mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.SOLID);
-                mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-            }
-        } else if (action instanceof PlaybackControlsRow.ThumbsDownAction) {
-            if (mThumbsDownAction.getIndex() == PlaybackControlsRow.ThumbsAction.SOLID) {
-                mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-            } else {
-                mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.SOLID);
-                mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
-            }
-        }
-        onMetadataChanged();
-    }
-
-    @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
-        // This method is overridden in order to make implement fast forwarding and rewinding when
-        // the user keeps the corresponding action pressed.
-        // We only consume DPAD_CENTER Action_DOWN events on the Fast-Forward and Rewind action and
-        // only if it has not been pressed in the last X milliseconds.
-        boolean consume = mSelectedAction instanceof PlaybackControlsRow.RewindAction;
-        consume = consume || mSelectedAction instanceof PlaybackControlsRow.FastForwardAction;
-        consume = consume && mInitialized;
-        consume = consume && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER;
-        consume = consume && event.getAction() == KeyEvent.ACTION_DOWN;
-        consume = consume && System
-                .currentTimeMillis() - mLastKeyDownEvent > FAST_FORWARD_REWIND_REPEAT_DELAY;
-        if (consume) {
-            mLastKeyDownEvent = System.currentTimeMillis();
-            int newPosition = getCurrentPosition() + FAST_FORWARD_REWIND_STEP;
-            if (mSelectedAction instanceof PlaybackControlsRow.RewindAction) {
-                newPosition = getCurrentPosition() - FAST_FORWARD_REWIND_STEP;
-            }
-            // Make sure the new calculated duration is in the range 0 >= X >= MediaDuration
-            if (newPosition < 0) newPosition = 0;
-            if (newPosition > getMediaDuration()) newPosition = getMediaDuration();
-            seekTo(newPosition);
-            return true;
-        }
-        return super.onKey(v, keyCode, event);
-    }
-
-    @Override public boolean hasValidMedia() {
-        return mMetaData != null;
-    }
-
-    @Override public boolean isMediaPlaying() {
-        return mPlayer.isPlaying();
-    }
-
-    @Override public CharSequence getMediaTitle() {
-        return hasValidMedia() ? mMetaData.getTitle() : "N/a";
-    }
-
-    @Override public CharSequence getMediaSubtitle() {
-        return hasValidMedia() ? mMetaData.getArtist() : "N/a";
-    }
-
-    @Override public int getMediaDuration() {
-        return mInitialized ? mPlayer.getDuration() : 0;
-    }
-
-    @Override public Drawable getMediaArt() {
-        return hasValidMedia() ? mMetaData.getCover() : null;
-    }
-
-    @Override public long getSupportedActions() {
-        return PlaybackControlGlue.ACTION_PLAY_PAUSE | PlaybackControlGlue.ACTION_FAST_FORWARD | PlaybackControlGlue.ACTION_REWIND;
-    }
-
-    @Override public int getCurrentSpeedId() {
-        // 0 = Pause, 1 = Normal Playback Speed
-        return mPlayer.isPlaying() ? 1 : 0;
-    }
-
-    @Override public int getCurrentPosition() {
-        return mInitialized ? mPlayer.getCurrentPosition() : 0;
-    }
-
-    @Override protected void startPlayback(int speed) throws IllegalStateException {
-        mPlayer.start();
-    }
-
-    @Override protected void pausePlayback() {
-        if (mPlayer.isPlaying()) {
-            mPlayer.pause();
-        }
-    }
-
-    @Override protected void skipToNext() {
-        // Not supported.
-    }
-
-    @Override protected void skipToPrevious() {
-        // Not supported.
-    }
-
-    /**
-     * Called whenever the user presses fast-forward/rewind or when the user keeps the corresponding
-     * action pressed.
-     *
-     * @param newPosition The new position of the media track in milliseconds.
-     */
-    protected void seekTo(int newPosition) {
-        mPlayer.seekTo(newPosition);
-    }
-
-    /**
-     * Sets the media source of the player witha given URI.
-     * @see MediaPlayer#setDataSource(String)
-     * @return Returns <code>true</code> if uri represents a new media; <code>false</code>
-     * otherwise.
-     */
-    public boolean setMediaSource(Uri uri) {
-        if (mMediaSourceUri != null && mMediaSourceUri.equals(uri)) {
-            return false;
-        }
-        mMediaSourceUri = uri;
-        return true;
-    }
-
-    /**
-     * Sets the media source of the player with a String path URL.
-     * @see MediaPlayer#setDataSource(String)
-     * @return Returns <code>true</code> if path represents a new media; <code>false</code>
-     * otherwise.
-     */
-    public boolean setMediaSource(String path) {
-        if (mMediaSourcePath != null && mMediaSourcePath.equals(mMediaSourcePath)) {
-            return false;
-        }
-        mMediaSourcePath = path;
-        return true;
-    }
-
-    public void prepareMediaForPlaying() {
-        reset();
-        try {
-            if (mMediaSourceUri != null) mPlayer.setDataSource(getContext(), mMediaSourceUri);
-            else mPlayer.setDataSource(mMediaSourcePath);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
-        mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override public void onPrepared(MediaPlayer mp) {
-                mInitialized = true;
-                mPlayer.start();
-                onMetadataChanged();
-                onStateChanged();
-                updateProgress();
-            }
-        });
-        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override public void onCompletion(MediaPlayer mp) {
-                if (mInitialized && mMediaFileFinishedPlayingListener != null)
-                    mMediaFileFinishedPlayingListener.onMediaFileFinishedPlaying(mMetaData);
-            }
-        });
-        mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
-            @Override public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
-            }
-        });
-        mPlayer.prepareAsync();
-        onStateChanged();
-    }
-
-    /**
-     * Call to <code>startPlayback(1)</code>.
-     *
-     * @throws IllegalStateException See {@link MediaPlayer} for further information about it's
-     * different states when setting a data source and preparing it to be played.
-     */
-    public void startPlayback() throws IllegalStateException {
-        startPlayback(1);
-    }
-
-    /**
-     * @return Returns <code>true</code> iff 'Shuffle' is <code>ON</code>.
-     */
-    public boolean useShuffle() {
-        return mShuffleAction.getIndex() == PlaybackControlsRow.ShuffleAction.ON;
-    }
-
-    /**
-     * @return Returns <code>true</code> iff 'Repeat-One' is <code>ON</code>.
-     */
-    public boolean repeatOne() {
-        return mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.ONE;
-    }
-
-    /**
-     * @return Returns <code>true</code> iff 'Repeat-All' is <code>ON</code>.
-     */
-    public boolean repeatAll() {
-        return mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.ALL;
-    }
-
-    public void setMetaData(MetaData metaData) {
-        mMetaData = metaData;
-        onMetadataChanged();
-    }
-
-    /**
-     * This is a listener implementation for the {@link OnItemViewSelectedListener} of the {@link
-     * PlaybackOverlayFragment}. This implementation is required in order to detect KEY_DOWN events
-     * on the {@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction} and
-     * {@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}. Thus you should
-     * <u>NOT</u> set another {@link OnItemViewSelectedListener} on your {@link
-     * PlaybackOverlayFragment}. Instead, override this method and call its super (this)
-     * implementation.
-     *
-     * @see OnItemViewSelectedListener#onItemSelected(Presenter.ViewHolder, Object,
-     * RowPresenter.ViewHolder, Row)
-     */
-    @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                                         RowPresenter.ViewHolder rowViewHolder, Row row) {
-        if (item instanceof Action) {
-            mSelectedAction = (Action) item;
-        } else {
-            mSelectedAction = null;
-        }
-    }
-
-    /**
-     * A listener which will be called whenever a track is finished playing.
-     */
-    public interface OnMediaFileFinishedPlayingListener {
-
-        /**
-         * Called when a track is finished playing.
-         *
-         * @param metaData The track's {@link MetaData} which just finished playing.
-         */
-        void onMediaFileFinishedPlaying(MetaData metaData);
-
-    }
-
-    /**
-     * Holds the meta data such as track title, artist and cover art. It'll be used by the {@link
-     * MediaPlayerGlue}.
-     */
-    public static class MetaData {
-
-        private String mTitle;
-        private String mArtist;
-        private Drawable mCover;
-
-        public String getTitle() {
-            return mTitle;
-        }
-
-        public void setTitle(String title) {
-            this.mTitle = title;
-        }
-
-        public String getArtist() {
-            return mArtist;
-        }
-
-        public void setArtist(String artist) {
-            this.mArtist = artist;
-        }
-
-        public Drawable getCover() {
-            return mCover;
-        }
-
-        public void setCover(Drawable cover) {
-            this.mCover = cover;
-        }
-
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java
deleted file mode 100644
index 0650cfc..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicConsumptionExampleFragment.java
+++ /dev/null
@@ -1,313 +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.supportleanbackshowcase.app.media;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v17.leanback.app.PlaybackOverlayFragment;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Constants;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.supportleanbackshowcase.models.Song;
-import android.support.v17.leanback.supportleanbackshowcase.models.SongList;
-import android.support.v17.leanback.widget.*;
-import android.support.v17.leanback.widget.AbstractMediaItemPresenter;
-import android.util.Log;
-
-import com.google.gson.Gson;
-
-import java.util.List;
-
-/**
- * This example shows how to play music files and build a simple track list.
- */
-public class MusicConsumptionExampleFragment extends PlaybackOverlayFragment implements
-        BaseOnItemViewClickedListener, BaseOnItemViewSelectedListener,
-        MediaPlayerGlue.OnMediaFileFinishedPlayingListener {
-
-    private static final String TAG = "MusicConsumptionExampleFragment";
-    private static final int PLAYLIST_ACTION_ID = 0;
-    private static final int FAVORITE_ACTION_ID = 1;
-    private ArrayObjectAdapter mRowsAdapter;
-    private MediaPlayerGlue mGlue;
-    private int mCurrentSongIndex = 0;
-    private List<Song> mSongList;
-    private boolean mAdapterNotified = false;
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (Constants.LOCAL_LOGD) Log.d(TAG, "onCreate");
-
-        mGlue = new MediaPlayerGlue(getActivity(), this) {
-
-            @Override protected void onRowChanged(PlaybackControlsRow row) {
-                if (mRowsAdapter == null || mAdapterNotified) return;
-                //mAdapterNotified = true;
-                mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
-            }
-        };
-        mGlue.setOnMediaFileFinishedPlayingListener(this);
-
-        String json = Utils.inputStreamToString(
-                getResources().openRawResource(R.raw.music_consumption_example));
-
-
-        mSongList = new Gson().fromJson(json, SongList.class).getSongs();
-
-        Resources res = getActivity().getResources();
-
-        // For each song add a playlist and favorite actions.
-        for(Song song : mSongList) {
-            MultiActionsProvider.MultiAction[] mediaRowActions = new
-                    MultiActionsProvider.MultiAction[2];
-            MultiActionsProvider.MultiAction playlistAction = new
-                    MultiActionsProvider.MultiAction(PLAYLIST_ACTION_ID);
-            Drawable[] playlistActionDrawables = new Drawable[] {
-                    res.getDrawable(R.drawable.ic_playlist_add_white_24dp,
-                            getActivity().getTheme()),
-                    res.getDrawable(R.drawable.ic_playlist_add_filled_24dp,
-                            getActivity().getTheme())};
-            playlistAction.setDrawables(playlistActionDrawables);
-            mediaRowActions[0] = playlistAction;
-
-            MultiActionsProvider.MultiAction favoriteAction = new
-                    MultiActionsProvider.MultiAction(FAVORITE_ACTION_ID);
-            Drawable[] favoriteActionDrawables = new Drawable[] {
-                    res.getDrawable(R.drawable.ic_favorite_border_white_24dp,
-                            getActivity().getTheme()),
-                    res.getDrawable(R.drawable.ic_favorite_filled_24dp,
-                            getActivity().getTheme())};
-            favoriteAction.setDrawables(favoriteActionDrawables);
-            mediaRowActions[1] = favoriteAction;
-            song.setMediaRowActions(mediaRowActions);
-        }
-
-        Song song = mSongList.get(mCurrentSongIndex);
-        MediaPlayerGlue.MetaData metaData = new MediaPlayerGlue.MetaData();
-        metaData.setArtist(song.getDescription());
-        metaData.setTitle(song.getTitle());
-        metaData.setCover(getResources().getDrawable(song.getImageResource(getActivity()), null));
-        Uri uri = Utils.getResourceUri(getActivity(), song.getFileResource(getActivity()));
-        mGlue.setMetaData(metaData);
-        mGlue.setMediaSource(uri);
-        mGlue.prepareMediaForPlaying();
-
-        addPlaybackControlsRow();
-    }
-
-    @Override public void onStart() {
-        super.onStart();
-        mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
-    }
-
-    @Override public void onStop() {
-        super.onStop();
-        mGlue.enableProgressUpdating(false);
-        mGlue.reset();
-    }
-
-    static class SongPresenter extends AbstractMediaItemPresenter {
-
-        SongPresenter() {
-            super();
-        }
-
-        SongPresenter(Context context, int themeResId) {
-            super(themeResId);
-            setHasMediaRowSeparator(true);
-        }
-
-        @Override
-        protected void onBindMediaDetails(ViewHolder vh, Object item) {
-
-            int favoriteTextColor =  vh.view.getContext().getResources().getColor(
-                    R.color.song_row_favorite_color);
-            Song song = (Song) item;
-            vh.getMediaItemNumberView().setText("" + song.getNumber());
-
-            String songTitle = song.getTitle() + " / " + song.getDescription();
-            vh.getMediaItemNameView().setText(songTitle);
-
-            vh.getMediaItemDurationView().setText("" + song.getDuration());
-
-            if (song.isFavorite()) {
-                vh.getMediaItemNumberView().setTextColor(favoriteTextColor);
-                vh.getMediaItemNameView().setTextColor(favoriteTextColor);
-                vh.getMediaItemDurationView().setTextColor(favoriteTextColor);
-            } else {
-                Context context = vh.getMediaItemNumberView().getContext();
-                vh.getMediaItemNumberView().setTextAppearance(context,
-                        R.style.TextAppearance_Leanback_PlaybackMediaItemNumber);
-                vh.getMediaItemNameView().setTextAppearance(context,
-                        R.style.TextAppearance_Leanback_PlaybackMediaItemName);
-                vh.getMediaItemDurationView().setTextAppearance(context,
-                        R.style.TextAppearance_Leanback_PlaybackMediaItemDuration);
-            }
-        }
-    };
-
-    static class SongPresenterSelector extends PresenterSelector {
-        Presenter mRegularPresenter;
-        Presenter mFavoritePresenter;
-
-        /**
-         * Adds a presenter to be used for the given class.
-         */
-        public SongPresenterSelector setSongPresenterRegular(Presenter presenter) {
-            mRegularPresenter = presenter;
-            return this;
-        }
-
-        /**
-         * Adds a presenter to be used for the given class.
-         */
-        public SongPresenterSelector setSongPresenterFavorite(Presenter presenter) {
-            mFavoritePresenter = presenter;
-            return this;
-        }
-
-        @Override
-        public Presenter[] getPresenters() {
-            return new Presenter[]{mRegularPresenter, mFavoritePresenter};
-        }
-
-        @Override
-        public Presenter getPresenter(Object item) {
-            return ( (Song) item).isFavorite() ? mFavoritePresenter : mRegularPresenter;
-        }
-
-    }
-
-    static class TrackListHeaderPresenter extends AbstractMediaListHeaderPresenter {
-
-        TrackListHeaderPresenter() {
-            super();
-        }
-
-        @Override
-        protected void onBindMediaListHeaderViewHolder(ViewHolder vh, Object item) {
-            vh.getHeaderView().setText("Tracklist");
-        }
-    };
-
-    private void addPlaybackControlsRow() {
-        mRowsAdapter = new ArrayObjectAdapter(new ClassPresenterSelector()
-                .addClassPresenterSelector(Song.class, new SongPresenterSelector()
-                        .setSongPresenterRegular(new SongPresenter(getActivity(),
-                                R.style.Theme_Example_LeanbackMusic_RegularSongNumbers))
-                        .setSongPresenterFavorite(new SongPresenter(getActivity(),
-                                R.style.Theme_Example_LeanbackMusic_FavoriteSongNumbers)))
-                .addClassPresenter(TrackListHeader.class, new TrackListHeaderPresenter())
-                .addClassPresenter(PlaybackControlsRow.class,
-                        mGlue.createControlsRowAndPresenter()));
-        mRowsAdapter.add(mGlue.getControlsRow());
-        mRowsAdapter.add(new TrackListHeader());
-        mRowsAdapter.addAll(2, mSongList);
-        setAdapter(mRowsAdapter);
-        setOnItemViewClickedListener(this);
-        setOnItemViewSelectedListener(this);
-    }
-
-    public MusicConsumptionExampleFragment() {
-        super();
-    }
-
-
-
-    @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                        RowPresenter.ViewHolder rowViewHolder, Object row) {
-
-        if (item instanceof  Action) {
-            // if the clicked item is a primary or secondary action in the playback controller
-            mGlue.onActionClicked((Action) item);
-        } else if (row instanceof  Song) {
-            // if a media item row is clicked
-            Song clickedSong = (Song) row;
-            AbstractMediaItemPresenter.ViewHolder songRowVh =
-                    (AbstractMediaItemPresenter.ViewHolder) rowViewHolder;
-
-            // if an action within a media item row is clicked
-            if (item instanceof MultiActionsProvider.MultiAction) {
-                if ( ((MultiActionsProvider.MultiAction) item).getId() == FAVORITE_ACTION_ID) {
-                    MultiActionsProvider.MultiAction favoriteAction =
-                            (MultiActionsProvider.MultiAction) item;
-                    MultiActionsProvider.MultiAction playlistAction =
-                            songRowVh.getMediaItemRowActions()[0];
-                    favoriteAction.incrementIndex();
-                    playlistAction.incrementIndex();;
-
-                    clickedSong.setFavorite(!clickedSong.isFavorite());
-                    songRowVh.notifyDetailsChanged();
-                    songRowVh.notifyActionChanged(playlistAction);
-                    songRowVh.notifyActionChanged(favoriteAction);
-                }
-            } else if (item == null){
-                // if a media item details is clicked, start playing that media item
-                onSongDetailsClicked(clickedSong);
-            }
-
-        }
-
-
-    }
-
-    @Override
-    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                               RowPresenter.ViewHolder rowViewHolder, Object row) {
-    }
-
-
-    public void onSongDetailsClicked(Song song) {
-        int nextSongIndex = mSongList.indexOf(song);
-        mCurrentSongIndex = nextSongIndex;
-        startPlayback();
-    }
-
-
-    @Override public void onMediaFileFinishedPlaying(MediaPlayerGlue.MetaData song) {
-        if (mGlue.repeatOne()) {
-        } else if (mGlue.useShuffle()) {
-            mCurrentSongIndex = (int) (Math.random() * mSongList.size());
-        } else {
-            mCurrentSongIndex++;
-            if (mCurrentSongIndex >= mSongList.size()) {
-                mCurrentSongIndex = 0;
-                if (!mGlue.repeatAll()) {
-                    return;
-                }
-            }
-        }
-        startPlayback();
-    }
-
-    private void startPlayback() {
-        Song song = mSongList.get(mCurrentSongIndex);
-        MediaPlayerGlue.MetaData metaData = new MediaPlayerGlue.MetaData();
-        metaData.setArtist(song.getDescription());
-        metaData.setTitle(song.getTitle());
-        metaData.setCover(getResources().getDrawable(song.getImageResource(getActivity()), null));
-
-        Uri uri = Utils.getResourceUri(getActivity(), song.getFileResource(getActivity()));
-        mGlue.setMetaData(metaData);
-
-        if (mGlue.setMediaSource(uri)) {
-            mGlue.prepareMediaForPlaying();
-        }
-        mGlue.startPlayback();
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicExampleActivity.java
deleted file mode 100644
index 3107ed6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MusicExampleActivity.java
+++ /dev/null
@@ -1,30 +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.supportleanbackshowcase.app.media;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * TODO: Javadoc
- */
-public class MusicExampleActivity extends Activity {
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_music_example);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/SongListRow.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/SongListRow.java
deleted file mode 100644
index 4096132..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/SongListRow.java
+++ /dev/null
@@ -1,20 +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.supportleanbackshowcase.app.media;
-
-import android.support.v17.leanback.widget.Row;
-
-public class SongListRow extends Row {}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java
deleted file mode 100644
index 0206748..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/TrackListHeader.java
+++ /dev/null
@@ -1,21 +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.supportleanbackshowcase.app.media;
-
-import android.support.v17.leanback.widget.Row;
-
-public class TrackListHeader extends Row {
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoConsumptionExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoConsumptionExampleFragment.java
deleted file mode 100644
index ca33b42..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoConsumptionExampleFragment.java
+++ /dev/null
@@ -1,120 +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.supportleanbackshowcase.app.media;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v17.leanback.app.PlaybackOverlayFragment;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-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.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-
-public class VideoConsumptionExampleFragment extends PlaybackOverlayFragment implements
-        OnItemViewClickedListener, MediaPlayerGlue.OnMediaFileFinishedPlayingListener {
-
-    private static final String URL = "http://techslides.com/demos/sample-videos/small.mp4";
-    public static final String TAG = "VideoConsumptionExampleFragment";
-    private ArrayObjectAdapter mRowsAdapter;
-    private MediaPlayerGlue mGlue;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mGlue = new VideoMediaPlayerGlue(getActivity(), this) {
-
-            @Override
-            protected void onRowChanged(PlaybackControlsRow row) {
-                if (mRowsAdapter == null) return;
-                mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
-            }
-        };
-        mGlue.setOnMediaFileFinishedPlayingListener(this);
-        MediaPlayerGlue.MetaData metaData = new MediaPlayerGlue.MetaData();
-        metaData.setArtist("A Googler");
-        metaData.setTitle("Diving with Sharks");
-        mGlue.setMetaData(metaData);
-        mGlue.setMediaSource(URL);
-        mGlue.prepareMediaForPlaying();
-
-
-        Fragment videoSurfaceFragment = getFragmentManager()
-                .findFragmentByTag(VideoSurfaceFragment.TAG);
-
-        SurfaceView surface = (SurfaceView) videoSurfaceFragment.getView();
-        surface.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                mGlue.setDisplay(holder);
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-                // Nothing to do
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-            }
-        });
-
-        setBackgroundType(PlaybackOverlayFragment.BG_LIGHT);
-        addPlaybackControlsRow();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mGlue.enableProgressUpdating(false);
-        mGlue.reset();
-    }
-
-    private void addPlaybackControlsRow() {
-        final PlaybackControlsRowPresenter controlsPresenter = mGlue
-                .createControlsRowAndPresenter();
-        mRowsAdapter = new ArrayObjectAdapter(controlsPresenter);
-        mRowsAdapter.add(mGlue.getControlsRow());
-        setAdapter(mRowsAdapter);
-        setOnItemViewClickedListener(this);
-    }
-
-    @Override
-    public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                              RowPresenter.ViewHolder rowViewHolder, Row row) {
-        if (!(item instanceof Action)) return;
-        mGlue.onActionClicked((Action) item);
-    }
-
-
-    @Override
-    public void onMediaFileFinishedPlaying(MediaPlayerGlue.MetaData metaData) {
-        mGlue.startPlayback();
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoExampleActivity.java
deleted file mode 100644
index 2e87654..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoExampleActivity.java
+++ /dev/null
@@ -1,43 +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.supportleanbackshowcase.app.media;
-
-import android.app.Activity;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * TODO: Javadoc
- */
-public class VideoExampleActivity extends Activity {
-
-    public static final String TAG = "VideoExampleActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_video_example);
-
-        FragmentTransaction ft1 = getFragmentManager().beginTransaction();
-        ft1.replace(R.id.videoFragment, new VideoSurfaceFragment(), VideoSurfaceFragment.TAG);
-        ft1.commit();
-
-        FragmentTransaction ft2 = getFragmentManager().beginTransaction();
-        ft2.add(R.id.videoFragment, new VideoConsumptionExampleFragment(), VideoConsumptionExampleFragment.TAG);
-        ft2.commit();
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoMediaPlayerGlue.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoMediaPlayerGlue.java
deleted file mode 100644
index 52ab09f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoMediaPlayerGlue.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.supportleanbackshowcase.app.media;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.support.v17.leanback.app.PlaybackOverlayFragment;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.MediaPlayerGlue;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-
-public abstract class VideoMediaPlayerGlue extends MediaPlayerGlue {
-
-    private final PlaybackControlsRow.ClosedCaptioningAction mClosedCaptioningAction;
-
-    public VideoMediaPlayerGlue(Context context, PlaybackOverlayFragment fragment) {
-        super(context, fragment);
-
-        // Instantiate secondary actions
-        mClosedCaptioningAction = new PlaybackControlsRow.ClosedCaptioningAction(context);
-        setFadingEnabled(true);
-    }
-
-    @Override protected void addSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
-        secondaryActionsAdapter.add(mClosedCaptioningAction);
-        secondaryActionsAdapter.add(mThumbsDownAction);
-        secondaryActionsAdapter.add(mThumbsUpAction);
-    }
-
-    @Override public void onActionClicked(Action action) {
-        super.onActionClicked(action);
-        if (action == mClosedCaptioningAction) {
-            mClosedCaptioningAction.nextIndex();
-        }
-    }
-
-    public void setupControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
-        // TODO: hahnr@ move into resources
-        presenter.setProgressColor(Color.parseColor("#EEFF41"));
-        presenter.setBackgroundColor(Color.parseColor("#007236"));
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoSurfaceFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoSurfaceFragment.java
deleted file mode 100644
index 569309d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/VideoSurfaceFragment.java
+++ /dev/null
@@ -1,41 +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.supportleanbackshowcase.app.media;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-
-public class VideoSurfaceFragment extends Fragment {
-
-    public static final String TAG = "VideoSurfaceFragment";
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        Log.d(TAG, "onCreate started");
-        super.onCreate(savedInstanceState);
-
-    }
-
-    @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                                                 Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.video_surface_fragment, null);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java
deleted file mode 100644
index 10ca3e9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/CustomTitleView.java
+++ /dev/null
@@ -1,88 +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.supportleanbackshowcase.app.page;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-/**
- * Custom title view to be used in {@link android.support.v17.leanback.app.BrowseFragment}.
- */
-public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.Provider {
-    private final TextView mTitleView;
-    private final ImageView mBadgeView;
-
-    private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
-        @Override
-        public View getSearchAffordanceView() {
-            return null;
-        }
-
-        @Override
-        public void setTitle(CharSequence titleText) {
-            CustomTitleView.this.setTitle(titleText);
-        }
-
-        @Override
-        public void setBadgeDrawable(Drawable drawable) {
-            CustomTitleView.this.setBadgeDrawable(drawable);
-        }
-    };
-
-    public CustomTitleView(Context context) {
-        this(context, null);
-    }
-
-    public CustomTitleView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        View root  = LayoutInflater.from(context).inflate(R.layout.custom_titleview, this);
-        mTitleView = (TextView) root.findViewById(R.id.title_tv);
-        mBadgeView = (ImageView)root.findViewById(R.id.title_badge_iv);
-    }
-
-    public void setTitle(CharSequence title) {
-        if (title != null) {
-            mTitleView.setText(title);
-            mTitleView.setVisibility(View.VISIBLE);
-            mBadgeView.setVisibility(View.GONE);
-        }
-    }
-
-
-    public void setBadgeDrawable(Drawable drawable) {
-        if (drawable != null) {
-            mTitleView.setVisibility(View.GONE);
-            mBadgeView.setImageDrawable(drawable);
-            mBadgeView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    @Override
-    public TitleViewAdapter getTitleViewAdapter() {
-        return mTitleViewAdapter;
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java
deleted file mode 100644
index a1a25a5..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/GridFragment.java
+++ /dev/null
@@ -1,219 +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.supportleanbackshowcase.app.page;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-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 rendering items in a vertical grids.
- */
-public class GridFragment extends Fragment implements BrowseFragment.MainFragmentAdapterProvider {
-    private static final String TAG = "VerticalGridFragment";
-    private static boolean DEBUG = false;
-
-    private ObjectAdapter mAdapter;
-    private VerticalGridPresenter mGridPresenter;
-    private VerticalGridPresenter.ViewHolder mGridViewHolder;
-    private OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
-    private Object mSceneAfterEntranceTransition;
-    private int mSelectedPosition = -1;
-    private BrowseFragment.MainFragmentAdapter mMainFragmentAdapter =
-            new BrowseFragment.MainFragmentAdapter(this) {
-                @Override
-                public void setEntranceTransitionState(boolean state) {
-                    GridFragment.this.setEntranceTransitionState(state);
-                }
-            };
-    /**
-     * 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;
-    }
-
-    private void gridOnItemSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSelectedPosition = position;
-            showOrHideTitle();
-        }
-    }
-
-    private void showOrHideTitle() {
-        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
-                == null) {
-            return;
-        }
-        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
-            mMainFragmentAdapter.getFragmentHost().showTitleView(true);
-        } else {
-            mMainFragmentAdapter.getFragmentHost().showTitleView(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) {
-        return inflater.inflate(R.layout.grid_fragment, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        ViewGroup gridDock = (ViewGroup) view.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);
-            }
-        });
-
-        getMainFragmentAdapter().getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
-        updateAdapter();
-
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mGridViewHolder = null;
-    }
-
-    @Override
-    public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
-        return mMainFragmentAdapter;
-    }
-
-    /**
-     * 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);
-            }
-        }
-    }
-
-    void setEntranceTransitionState(boolean afterTransition) {
-        mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowActivity.java
deleted file mode 100644
index 4d5b309..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowActivity.java
+++ /dev/null
@@ -1,32 +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.supportleanbackshowcase.app.page;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * Activity showcasing the use of {@link android.support.v17.leanback.widget.PageRow} and
- * {@link android.support.v17.leanback.widget.ListRow}.
- */
-public class PageAndListRowActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.page_list_row);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java
deleted file mode 100644
index 3f08a59..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/PageAndListRowFragment.java
+++ /dev/null
@@ -1,338 +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.supportleanbackshowcase.app.page;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.app.RowsFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.app.details.ShadowRowPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.CardPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.CardListRow;
-import android.support.v17.leanback.supportleanbackshowcase.utils.Utils;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.FocusHighlight;
-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.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.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-import com.google.gson.Gson;
-
-/**
- * Sample {@link BrowseFragment} implementation showcasing the use of {@link PageRow} and
- * {@link ListRow}.
- */
-public class PageAndListRowFragment extends BrowseFragment {
-    private static final long HEADER_ID_1 = 1;
-    private static final String HEADER_NAME_1 = "Page Fragment";
-    private static final long HEADER_ID_2 = 2;
-    private static final String HEADER_NAME_2 = "Rows Fragment";
-    private static final long HEADER_ID_3 = 3;
-    private static final String HEADER_NAME_3 = "Settings Fragment";
-    private static final long HEADER_ID_4 = 4;
-    private static final String HEADER_NAME_4 = "User agreement Fragment";
-    private BackgroundManager mBackgroundManager;
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setupUi();
-        loadData();
-        mBackgroundManager = BackgroundManager.getInstance(getActivity());
-        mBackgroundManager.attach(getActivity().getWindow());
-        getMainFragmentRegistry().registerFragment(PageRow.class,
-                new PageRowFragmentFactory(mBackgroundManager));
-    }
-
-    private void setupUi() {
-        setHeadersState(HEADERS_ENABLED);
-        setHeadersTransitionOnBackEnabled(true);
-        setBrandColor(getResources().getColor(R.color.fastlane_background));
-        setTitle("Title goes here");
-        setOnSearchClickedListener(new View.OnClickListener() {
-
-            @Override
-            public void onClick(View view) {
-                Toast.makeText(
-                        getActivity(), getString(R.string.implement_search), Toast.LENGTH_SHORT)
-                        .show();
-            }
-        });
-
-        prepareEntranceTransition();
-    }
-
-    private void loadData() {
-        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-        setAdapter(mRowsAdapter);
-
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                createRows();
-                startEntranceTransition();
-            }
-        }, 2000);
-    }
-
-    private void createRows() {
-        HeaderItem headerItem1 = new HeaderItem(HEADER_ID_1, HEADER_NAME_1);
-        PageRow pageRow1 = new PageRow(headerItem1);
-        mRowsAdapter.add(pageRow1);
-
-        HeaderItem headerItem2 = new HeaderItem(HEADER_ID_2, HEADER_NAME_2);
-        PageRow pageRow2 = new PageRow(headerItem2);
-        mRowsAdapter.add(pageRow2);
-
-        HeaderItem headerItem3 = new HeaderItem(HEADER_ID_3, HEADER_NAME_3);
-        PageRow pageRow3 = new PageRow(headerItem3);
-        mRowsAdapter.add(pageRow3);
-
-        HeaderItem headerItem4 = new HeaderItem(HEADER_ID_4, HEADER_NAME_4);
-        PageRow pageRow4 = new PageRow(headerItem4);
-        mRowsAdapter.add(pageRow4);
-    }
-
-    private static class PageRowFragmentFactory extends BrowseFragment.FragmentFactory {
-        private final BackgroundManager mBackgroundManager;
-
-        PageRowFragmentFactory(BackgroundManager backgroundManager) {
-            this.mBackgroundManager = backgroundManager;
-        }
-
-        @Override
-        public Fragment createFragment(Object rowObj) {
-            Row row = (Row)rowObj;
-            mBackgroundManager.setDrawable(null);
-            if (row.getHeaderItem().getId() == HEADER_ID_1) {
-                return new SampleFragmentA();
-            } else if (row.getHeaderItem().getId() == HEADER_ID_2) {
-                return new SampleFragmentB();
-            } else if (row.getHeaderItem().getId() == HEADER_ID_3) {
-                return new SettingsFragment();
-            } else if (row.getHeaderItem().getId() == HEADER_ID_4) {
-                return new WebViewFragment();
-            }
-
-            throw new IllegalArgumentException(String.format("Invalid row %s", rowObj));
-        }
-    }
-
-    public static class PageFragmentAdapterImpl extends MainFragmentAdapter<SampleFragmentA> {
-
-        public PageFragmentAdapterImpl(SampleFragmentA fragment) {
-            super(fragment);
-        }
-    }
-
-    /**
-     * Simple page fragment implementation.
-     */
-    public static class SampleFragmentA extends GridFragment {
-        private static final int COLUMNS = 4;
-        private final int ZOOM_FACTOR = FocusHighlight.ZOOM_FACTOR_SMALL;
-        private ArrayObjectAdapter mAdapter;
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setupAdapter();
-            loadData();
-            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
-        }
-
-
-        private void setupAdapter() {
-            VerticalGridPresenter presenter = new VerticalGridPresenter(ZOOM_FACTOR);
-            presenter.setNumberOfColumns(COLUMNS);
-            setGridPresenter(presenter);
-
-            CardPresenterSelector cardPresenter = new CardPresenterSelector(getActivity());
-            mAdapter = new ArrayObjectAdapter(cardPresenter);
-            setAdapter(mAdapter);
-
-            setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(
-                        Presenter.ViewHolder itemViewHolder,
-                        Object item,
-                        RowPresenter.ViewHolder rowViewHolder,
-                        Row row) {
-                    Card card = (Card)item;
-                    Toast.makeText(getActivity(),
-                            "Clicked on "+card.getTitle(),
-                            Toast.LENGTH_SHORT).show();
-                }
-            });
-        }
-
-        private void loadData() {
-            String json = Utils.inputStreamToString(getResources().openRawResource(
-                    R.raw.grid_example));
-            CardRow cardRow = new Gson().fromJson(json, CardRow.class);
-            mAdapter.addAll(0, cardRow.getCards());
-        }
-    }
-
-    /**
-     * Page fragment embeds a rows fragment.
-     */
-    public static class SampleFragmentB extends RowsFragment {
-        private final ArrayObjectAdapter mRowsAdapter;
-
-        public SampleFragmentB() {
-            mRowsAdapter = new ArrayObjectAdapter(new ShadowRowPresenterSelector());
-
-            setAdapter(mRowsAdapter);
-            setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(
-                        Presenter.ViewHolder itemViewHolder,
-                        Object item,
-                        RowPresenter.ViewHolder rowViewHolder,
-                        Row row) {
-                    Toast.makeText(getActivity(), "Implement click handler", Toast.LENGTH_SHORT)
-                            .show();
-                }
-            });
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            createRows();
-            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
-        }
-
-        private void createRows() {
-                String json = Utils.inputStreamToString(getResources().openRawResource(
-                        R.raw.page_row_example));
-                CardRow[] rows = new Gson().fromJson(json, CardRow[].class);
-                for (CardRow row : rows) {
-                    if (row.getType() == CardRow.TYPE_DEFAULT) {
-                        mRowsAdapter.add(createCardRow(row));
-                    }
-                }
-        }
-
-        private Row createCardRow(CardRow cardRow) {
-            PresenterSelector presenterSelector = new CardPresenterSelector(getActivity());
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
-            for (Card card : cardRow.getCards()) {
-                adapter.add(card);
-            }
-
-            HeaderItem headerItem = new HeaderItem(cardRow.getTitle());
-            return new CardListRow(headerItem, adapter, cardRow);
-        }
-    }
-
-    public static class SettingsFragment extends RowsFragment {
-        private final ArrayObjectAdapter mRowsAdapter;
-
-        public SettingsFragment() {
-            ListRowPresenter selector = new ListRowPresenter();
-            selector.setNumRows(2);
-            mRowsAdapter = new ArrayObjectAdapter(selector);
-            setAdapter(mRowsAdapter);
-        }
-
-        @Override
-        public void onAttach(Activity activity) {
-            super.onAttach(activity);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    loadData();
-                }
-            }, 200);
-        }
-
-        private void loadData() {
-            if (isAdded()) {
-                String json = Utils.inputStreamToString(getResources().openRawResource(
-                        R.raw.icon_example));
-                CardRow cardRow = new Gson().fromJson(json, CardRow.class);
-                mRowsAdapter.add(createCardRow(cardRow));
-                getMainFragmentAdapter().getFragmentHost().notifyDataReady(
-                        getMainFragmentAdapter());
-            }
-        }
-
-        private ListRow createCardRow(CardRow cardRow) {
-            SettingsIconPresenter iconCardPresenter = new SettingsIconPresenter(getActivity());
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(iconCardPresenter);
-            for(Card card : cardRow.getCards()) {
-                adapter.add(card);
-            }
-
-            HeaderItem headerItem = new HeaderItem(cardRow.getTitle());
-            return new CardListRow(headerItem, adapter, cardRow);
-        }
-    }
-
-    public static class WebViewFragment extends Fragment implements MainFragmentAdapterProvider {
-        private MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
-        private WebView mWebview;
-
-        @Override
-        public MainFragmentAdapter getMainFragmentAdapter() {
-            return mMainFragmentAdapter;
-        }
-
-        @Override
-        public View onCreateView(
-                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-            FrameLayout root = new FrameLayout(getActivity());
-            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                    FrameLayout.LayoutParams.MATCH_PARENT,
-                    FrameLayout.LayoutParams.MATCH_PARENT);
-            lp.setMarginStart(32);
-            mWebview = new WebView(getActivity());
-            mWebview.setWebViewClient(new WebViewClient());
-            mWebview.getSettings().setJavaScriptEnabled(true);
-            root.addView(mWebview, lp);
-            return root;
-        }
-
-        @Override
-        public void onResume() {
-            super.onResume();
-            mWebview.loadUrl("https://www.google.com/policies/terms");
-            getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter());
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/SettingsIconPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/SettingsIconPresenter.java
deleted file mode 100644
index cdc8447..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/page/SettingsIconPresenter.java
+++ /dev/null
@@ -1,53 +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.supportleanbackshowcase.app.page;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.cards.presenters.ImageCardViewPresenter;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.view.View;
-import android.widget.ImageView;
-
-/**
- * Simple presenter implementation to represent settings icon as cards.
- */
-public class SettingsIconPresenter extends ImageCardViewPresenter {
-
-    public SettingsIconPresenter(Context context) {
-        super(context, R.style.IconCardTheme);
-    }
-
-    @Override
-    protected ImageCardView onCreateView() {
-        final ImageCardView imageCardView = super.onCreateView();
-        imageCardView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (hasFocus) {
-                    setImageBackground(imageCardView, R.color.settings_card_background_focussed);
-                } else {
-                    setImageBackground(imageCardView, R.color.settings_card_background);
-                }
-            }
-        });
-        setImageBackground(imageCardView, R.color.settings_card_background);
-        return imageCardView;
-    }
-
-    private void setImageBackground(ImageCardView imageCardView, int colorId) {
-        imageCardView.setBackgroundColor(getContext().getResources().getColor(colorId));
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleActivity.java
deleted file mode 100644
index 01963ca..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleActivity.java
+++ /dev/null
@@ -1,31 +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.supportleanbackshowcase.app.settings;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/*
- * TODO: Javadoc
- */
-public class SettingsExampleActivity extends Activity {
-
-    @Override public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_settings_example);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleFragment.java
deleted file mode 100644
index 0276d53..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/settings/SettingsExampleFragment.java
+++ /dev/null
@@ -1,89 +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.supportleanbackshowcase.app.settings;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.preference.LeanbackPreferenceFragment;
-import android.support.v17.preference.LeanbackSettingsFragment;
-import android.support.v7.preference.DialogPreference;
-import android.support.v7.preference.ListPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceScreen;
-import android.widget.Toast;
-
-import java.util.Arrays;
-import java.util.Stack;
-
-public class SettingsExampleFragment extends LeanbackSettingsFragment {
-
-    @Override
-    public void onPreferenceStartInitialScreen() {
-        startPreferenceFragment(buildPreferenceFragment(R.xml.prefs, null));
-    }
-
-    @Override
-    public boolean onPreferenceStartFragment(PreferenceFragment preferenceFragment,
-                                             Preference preference) {
-        return false;
-    }
-
-    @Override
-    public boolean onPreferenceStartScreen(PreferenceFragment preferenceFragment,
-                                           PreferenceScreen preferenceScreen) {
-        PreferenceFragment frag = buildPreferenceFragment(R.xml.prefs, preferenceScreen.getKey());
-        startPreferenceFragment(frag);
-        return true;
-    }
-
-    private PreferenceFragment buildPreferenceFragment(int preferenceResId, String root) {
-        PreferenceFragment fragment = new PrefFragment();
-        Bundle args = new Bundle();
-        args.putInt("preferenceResource", preferenceResId);
-        args.putString("root", root);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    public static class PrefFragment extends LeanbackPreferenceFragment {
-
-        @Override
-        public void onCreatePreferences(Bundle bundle, String s) {
-            String root = getArguments().getString("root", null);
-            int prefResId = getArguments().getInt("preferenceResource");
-            if (root == null) {
-                addPreferencesFromResource(prefResId);
-            } else {
-                setPreferencesFromResource(prefResId, root);
-            }
-        }
-
-        @Override
-        public boolean onPreferenceTreeClick(Preference preference) {
-            final String[] keys = {"prefs_wifi_connect_wps", "prefs_date", "prefs_time",
-                    "prefs_date_time_use_timezone", "app_banner_sample_app", "pref_force_stop",
-                    "pref_uninstall", "pref_more_info"};
-            if (Arrays.asList(keys).contains(preference.getKey())) {
-                Toast.makeText(getActivity(), "Implement your own action handler.", Toast.LENGTH_SHORT).show();
-                return true;
-            }
-            return super.onPreferenceTreeClick(preference);
-        }
-
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample1stStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample1stStepFragment.java
deleted file mode 100644
index cbfa868..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample1stStepFragment.java
+++ /dev/null
@@ -1,70 +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.supportleanbackshowcase.app.wizard;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import java.util.List;
-
-/**
- * The first screen of the rental wizard. Gives the user the choice between renting the movie in SD
- * or HD quality.
- */
-public class WizardExample1stStepFragment extends WizardExampleBaseStepFragment {
-
-    private static final int ACTION_ID_BUY_HD = 1;
-    private static final int ACTION_ID_BUY_SD = ACTION_ID_BUY_HD + 1;
-
-    @NonNull
-    @Override
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(mMovie.getTitle(),
-                getString(R.string.wizard_example_choose_rent_options),
-                mMovie.getBreadcrump(), null);
-        return guidance;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        GuidedAction action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_BUY_HD)
-                .title(R.string.wizard_example_rent_hd)
-                .editable(false)
-                .description(mMovie.getPriceHd() + " " +
-                        getString(R.string.wizard_example_watch_hd))
-                .build();
-        actions.add(action);
-        action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_BUY_SD)
-                .title(getString(R.string.wizard_example_rent_sd))
-                .editable(false)
-                .description(mMovie.getPriceSd() + " " +
-                        getString(R.string.wizard_example_watch_sd))
-                .build();
-        actions.add(action);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        boolean rentHd = ACTION_ID_BUY_HD == action.getId();
-        GuidedStepFragment fragment = WizardExample2ndStepFragment.build(rentHd, this);
-        add(getFragmentManager(), fragment);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample2ndStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample2ndStepFragment.java
deleted file mode 100644
index f630d59..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample2ndStepFragment.java
+++ /dev/null
@@ -1,152 +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.supportleanbackshowcase.app.wizard;
-
-import android.app.FragmentManager;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.widget.Toast;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Displays the second screen of the rental wizard which requires the user to confirm his purchase.
- */
-public class WizardExample2ndStepFragment extends WizardExampleBaseStepFragment {
-
-    private static final String ARG_HD = "hd";
-    private static final int ACTION_ID_CONFIRM = 1;
-    private static final int ACTION_ID_PAYMENT_METHOD = ACTION_ID_CONFIRM + 1;
-    private static final int ACTION_ID_NEW_PAYMENT = ACTION_ID_PAYMENT_METHOD + 1;
-
-    protected static ArrayList<String> sCards = new ArrayList();
-    protected static int sSelectedCard = -1;
-
-    static {
-        sCards.add("Visa-1234");
-        sCards.add("Master-4321");
-    }
-
-
-    public static GuidedStepFragment build(boolean hd, WizardExampleBaseStepFragment previousFragment) {
-        GuidedStepFragment fragment = new WizardExample2ndStepFragment();
-        // Reuse the same arguments this fragment was given.
-        Bundle args = previousFragment.getArguments();
-        args.putBoolean(ARG_HD, hd);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @NonNull
-    @Override
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(mMovie.getTitle(),
-                getString(R.string.wizard_example_rental_period),
-                mMovie.getBreadcrump(), null);
-        return guidance;
-
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        boolean rentHighDefinition = getArguments().getBoolean(ARG_HD);
-
-        GuidedAction action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_CONFIRM)
-                .title(R.string.wizard_example_rent)
-                .description(rentHighDefinition ? mMovie.getPriceHd() : mMovie.getPriceSd())
-                .editable(false)
-                .build();
-        action.setEnabled(false);
-        actions.add(action);
-        List<GuidedAction> subActions = new ArrayList();
-        action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_PAYMENT_METHOD)
-                .title(R.string.wizard_example_payment_method)
-                .editTitle("")
-                .description(R.string.wizard_example_input_credit)
-                .subActions(subActions)
-                .build();
-        actions.add(action);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        GuidedAction payment = findActionById(ACTION_ID_PAYMENT_METHOD);
-
-        List<GuidedAction> paymentSubActions = payment.getSubActions();
-        paymentSubActions.clear();
-        for (int i = 0; i < sCards.size(); i++) {
-            paymentSubActions.add(new GuidedAction.Builder(getActivity())
-                            .title(sCards.get(i))
-                            .description("")
-                            .checkSetId(GuidedAction.DEFAULT_CHECK_SET_ID)
-                            .build()
-            );
-        }
-        paymentSubActions.add(new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_NEW_PAYMENT)
-                .title("Add New Card")
-                .description("")
-                .editable(false)
-                .build()
-        );
-        if ( sSelectedCard >= 0 && sSelectedCard < sCards.size() ) {
-            payment.setDescription(sCards.get(sSelectedCard));
-            findActionById(ACTION_ID_CONFIRM).setEnabled(true);
-        } else
-            findActionById(ACTION_ID_CONFIRM).setEnabled(false);
-        notifyActionChanged(findActionPositionById(ACTION_ID_CONFIRM));
-    }
-
-    @Override
-    public boolean onSubGuidedActionClicked(GuidedAction action) {
-
-        if (action.isChecked()) {
-            String payment = action.getTitle().toString();
-            if ( (sSelectedCard = sCards.indexOf(payment)) != -1 ) {
-                findActionById(ACTION_ID_PAYMENT_METHOD).setDescription(payment);
-                notifyActionChanged(findActionPositionById(ACTION_ID_PAYMENT_METHOD));
-                findActionById(ACTION_ID_CONFIRM).setEnabled(true);
-                notifyActionChanged(findActionPositionById(ACTION_ID_CONFIRM));
-            }
-            return true;
-        } else {
-            FragmentManager fm = getFragmentManager();
-            GuidedStepFragment fragment = new WizardNewPaymentStepFragment();
-            fragment.setArguments(getArguments());
-            add(fm, fragment);
-            return false;
-        }
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (ACTION_ID_CONFIRM == action.getId()) {
-            GuidedStepFragment fragment = new WizardExample3rdStepFragment();
-            fragment.setArguments(getArguments());
-            add(getFragmentManager(), fragment);
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample3rdStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample3rdStepFragment.java
deleted file mode 100644
index 9a2ec39..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample3rdStepFragment.java
+++ /dev/null
@@ -1,99 +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.supportleanbackshowcase.app.wizard;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-
-import java.util.List;
-
-/**
- * This is the third screen of the rental wizard which will display a progressbar while waiting for
- * the server to process the rental. The server communication is faked for the sake of this example
- * by waiting four seconds until continuing.
- */
-public class WizardExample3rdStepFragment extends WizardExampleBaseStepFragment {
-
-    private static final int ACTION_ID_PROCESSING = 1;
-    private final Handler mFakeHttpHandler = new Handler();
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        // Fake Http call by creating some sort of delay.
-        mFakeHttpHandler.postDelayed(fakeHttpRequestRunnable, 4000L);
-    }
-
-    @Override
-    public GuidedActionsStylist onCreateActionsStylist() {
-        GuidedActionsStylist stylist = new GuidedActionsStylist() {
-            @Override
-            public int onProvideItemLayoutId() {
-                return R.layout.wizard_progress_action_item;
-            }
-
-        };
-        return stylist;
-    }
-
-    @Override
-    public int onProvideTheme() {
-        return R.style.Theme_Example_LeanbackWizard_NoSelector;
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-
-        // Make sure to cancel the execution of the Runnable in case the fragment is stopped.
-        mFakeHttpHandler.removeCallbacks(fakeHttpRequestRunnable);
-    }
-
-    @NonNull
-    @Override
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(mMovie.getTitle(),
-                getString(R.string.wizard_example_just_a_second),
-                mMovie.getBreadcrump(), null);
-        return guidance;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        GuidedAction action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_PROCESSING)
-                .title(R.string.wizard_example_processing)
-                .infoOnly(true)
-                .build();
-        actions.add(action);
-    }
-
-    private final Runnable fakeHttpRequestRunnable = new Runnable() {
-        @Override
-        public void run() {
-            GuidedStepFragment fragment = new WizardExample4thStepFragment();
-            fragment.setArguments(getArguments());
-            add(getFragmentManager(), fragment);
-        }
-    };
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample4thStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample4thStepFragment.java
deleted file mode 100644
index b0cb9aa..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExample4thStepFragment.java
+++ /dev/null
@@ -1,77 +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.supportleanbackshowcase.app.wizard;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.app.media.VideoExampleActivity;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v4.app.ActivityOptionsCompat;
-import android.widget.Toast;
-
-import java.util.List;
-
-/**
- * The last screen of the Wizard gives to options to either watch the rented movie now or later. Due
- * to keep this example simple and focused on the usage of the GuidedStepFragment, clicking on
- * either action will end the wizard. You might however start a new Activity playing the movie.
- */
-public class WizardExample4thStepFragment extends WizardExampleBaseStepFragment {
-
-    private static final int ACTION_ID_WATCH = 1;
-    private static final int ACTION_ID_LATER = ACTION_ID_WATCH + 1;
-
-    @NonNull
-    @Override
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(mMovie.getTitle(),
-                getString(R.string.wizard_example_rental_period),
-                mMovie.getBreadcrump(), null);
-        return guidance;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        GuidedAction action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_WATCH)
-                .editable(false)
-                .title(R.string.wizard_example_watch_now)
-                .build();
-        actions.add(action);
-        action = new GuidedAction.Builder(getActivity())
-                .id(ACTION_ID_LATER)
-                .editable(false)
-                .title(R.string.wizard_example_later)
-                .build();
-        actions.add(action);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (action.getId() == ACTION_ID_WATCH) {
-            finishGuidedStepFragments();
-            Intent intent = new Intent(getActivity().getBaseContext(),
-                    VideoExampleActivity.class);
-            startActivity(intent);
-        } else if (action.getId() == ACTION_ID_LATER) {
-            Toast.makeText(getActivity(), getString(R.string.wizard_example_later_clicked),
-                    Toast.LENGTH_SHORT).show();
-            finishGuidedStepFragments();
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleActivity.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleActivity.java
deleted file mode 100644
index 96f71f8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleActivity.java
+++ /dev/null
@@ -1,48 +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.supportleanbackshowcase.app.wizard;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * An Activity displaying a wizard for renting a movie.
- */
-public class WizardExampleActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getWindow().setBackgroundDrawableResource(R.drawable.wizard_background_blackned);
-
-        GuidedStepFragment fragment = new WizardExample1stStepFragment();
-        fragment.setArguments(getIntent().getExtras()); // Delegate Movie to first step.
-        GuidedStepFragment.addAsRoot(this, fragment, android.R.id.content);
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (GuidedStepFragment.getCurrentGuidedStepFragment(getFragmentManager())
-                instanceof WizardExample4thStepFragment) {
-            // The user 'bought' the product. When he presses 'Back' the Wizard will be closed and
-            // he will not be send back to 'Processing Payment...'-Screen.
-            finish();
-        } else super.onBackPressed();
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleBaseStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleBaseStepFragment.java
deleted file mode 100644
index 8fd5981..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardExampleBaseStepFragment.java
+++ /dev/null
@@ -1,47 +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.supportleanbackshowcase.app.wizard;
-
-import android.os.Bundle;
-import android.support.v17.leanback.app.GuidedStepFragment;
-import android.support.v17.leanback.supportleanbackshowcase.models.Movie;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-
-/**
- * A base class which provides all it's implementations with a method #getWizardActivity(). It also
- * makes sure that the wizard is using the correct theme.
- */
-public abstract class WizardExampleBaseStepFragment extends GuidedStepFragment {
-
-    protected Movie mMovie;
-
-    @Override
-    public int onProvideTheme() {
-        return R.style.Theme_Example_LeanbackWizard;
-    }
-
-    WizardExampleActivity getWizardActivity() {
-        if (!(getActivity() instanceof WizardExampleActivity)) {
-            throw new IllegalStateException(WizardExampleActivity.class.getName() + " expected.");
-        }
-        return (WizardExampleActivity) getActivity();
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        mMovie = (Movie) getArguments().getSerializable("movie");
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardNewPaymentStepFragment.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardNewPaymentStepFragment.java
deleted file mode 100644
index a42ab9e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/wizard/WizardNewPaymentStepFragment.java
+++ /dev/null
@@ -1,149 +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.supportleanbackshowcase.app.wizard;
-
-import android.app.FragmentManager;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedDatePickerAction;
-import android.text.TextUtils;
-
-import java.util.Calendar;
-import java.util.List;
-
-/**
- * A fragment for allowing users to enter a new payment information.
- */
-public class WizardNewPaymentStepFragment extends WizardExampleBaseStepFragment {
-
-    private static final int ACTION_ID_CARD_NUMBER = 1;
-    private static final int ACTION_ID_PAYMENT_EXP = ACTION_ID_CARD_NUMBER + 1;
-
-    @NonNull
-    @Override
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        String title = getString(R.string.wizard_example_new_payment_guidance_title);
-        String description = getString(R.string.wizard_example_new_payment_guidance_description);
-        String breadcrumb = mMovie.getBreadcrump();
-
-        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
-                breadcrumb, null);
-        return guidance;
-    }
-
-    @Override
-    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        actions.add(new GuidedAction.Builder(getActivity())
-                        .id(ACTION_ID_CARD_NUMBER)
-                        .title(R.string.wizard_example_input_card)
-                        .editTitle("")
-                        .description(R.string.wizard_example_input_card)
-                        .editDescription("Card number")
-                        .editable(true)
-                        .build()
-        );
-
-        actions.add(new GuidedDatePickerAction.Builder(getActivity())
-                        .id(ACTION_ID_PAYMENT_EXP)
-                        .title(R.string.wizard_example_expiration_date)
-                        .datePickerFormat("MY")
-                        .build()
-        );
-    }
-
-    @Override
-    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
-                                      Bundle savedInstanceState) {
-        actions.add(new GuidedAction.Builder(getActivity())
-                        .clickAction(GuidedAction.ACTION_ID_OK)
-                        .build()
-        );
-        actions.get(actions.size() - 1).setEnabled(false);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (action.getId() == GuidedAction.ACTION_ID_OK) {
-            CharSequence cardNumber = findActionById(ACTION_ID_CARD_NUMBER).getDescription();
-            WizardExample2ndStepFragment.sSelectedCard = WizardExample2ndStepFragment.sCards.size();
-            WizardExample2ndStepFragment.sCards.add(cardNumber.toString());
-            popBackStackToGuidedStepFragment(WizardNewPaymentStepFragment.class,
-                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
-        }
-    }
-
-    @Override
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-
-        boolean cardNumberCheck = false;
-        boolean expDateCheck = false;
-
-        if (action.getId() == ACTION_ID_CARD_NUMBER) {
-            CharSequence cardNumber = action.getEditTitle();
-            cardNumberCheck = isCardNumberValid(cardNumber);
-            expDateCheck = isExpDateValid(findActionById(ACTION_ID_PAYMENT_EXP));
-            updateOkButton(cardNumberCheck && expDateCheck);
-
-            if (cardNumberCheck) {
-                String last4Digits = cardNumber.subSequence(cardNumber.length() - 4,
-                        cardNumber.length()).toString();
-
-                if ( (Integer.parseInt(last4Digits) & 1) == 0 )
-                    action.setDescription(getString(R.string.wizard_example_visa,
-                            last4Digits));
-                else
-                    action.setDescription(getString(R.string.wizard_example_master,
-                            last4Digits));
-
-                return GuidedAction.ACTION_ID_NEXT;
-            } else if (cardNumber.length() == 0) {
-                action.setDescription(getString(R.string.wizard_example_input_card));
-                return GuidedAction.ACTION_ID_CURRENT;
-            } else {
-                action.setDescription(getString(R.string.wizard_example_input_credit_wrong));
-                return GuidedAction.ACTION_ID_CURRENT;
-            }
-
-        } else if (action.getId() == ACTION_ID_PAYMENT_EXP) {
-            expDateCheck = isExpDateValid(action);
-            cardNumberCheck = isCardNumberValid(findActionById(ACTION_ID_CARD_NUMBER)
-                    .getEditTitle());
-            updateOkButton(cardNumberCheck && expDateCheck);
-            if (expDateCheck) {
-                return GuidedAction.ACTION_ID_NEXT;
-            }
-        }
-        return GuidedAction.ACTION_ID_CURRENT;
-    }
-
-    private void updateOkButton(boolean enabled) {
-        findButtonActionById(GuidedAction.ACTION_ID_OK).setEnabled(enabled);
-        notifyButtonActionChanged(findButtonActionPositionById(GuidedAction.ACTION_ID_OK));
-    }
-
-    private static boolean isCardNumberValid(CharSequence number) {
-        return (TextUtils.isDigitsOnly(number) && number.length() == 16);
-    }
-
-    private static boolean isExpDateValid(GuidedAction dateAction) {
-        long date = ((GuidedDatePickerAction) dateAction).getDate();
-        Calendar c = Calendar.getInstance();
-        c.setTimeInMillis(date);
-        return Calendar.getInstance().before(c);
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/CharacterCardView.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/CharacterCardView.java
deleted file mode 100644
index 89117b5..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/CharacterCardView.java
+++ /dev/null
@@ -1,69 +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.supportleanbackshowcase.cards;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.BaseCardView;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-public class CharacterCardView extends BaseCardView {
-
-    public CharacterCardView(Context context) {
-        super(context, null, R.style.CharacterCardStyle);
-        LayoutInflater.from(getContext()).inflate(R.layout.character_card, this);
-        setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                ImageView mainImage = findViewById(R.id.main_image);
-                View container = findViewById(R.id.container);
-                if (hasFocus) {
-                    container.setBackgroundResource(R.drawable.character_focused);
-                    mainImage.setBackgroundResource(R.drawable.character_focused);
-                } else {
-                    container.setBackgroundResource(R.drawable.character_not_focused_padding);
-                    mainImage.setBackgroundResource(R.drawable.character_not_focused);
-                }
-            }
-        });
-        setFocusable(true);
-    }
-
-    public void updateUi(Card card) {
-        TextView primaryText = findViewById(R.id.primary_text);
-        final ImageView imageView = findViewById(R.id.main_image);
-
-        primaryText.setText(card.getTitle());
-        if (card.getLocalImageResourceName() != null) {
-            int resourceId = card.getLocalImageResourceId(getContext());
-            Bitmap bitmap = BitmapFactory
-                    .decodeResource(getContext().getResources(), resourceId);
-            RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(getContext().getResources(), bitmap);
-            drawable.setAntiAlias(true);
-            drawable.setCornerRadius(Math.max(bitmap.getWidth(), bitmap.getHeight()) / 2.0f);
-            imageView.setImageDrawable(drawable);
-        }
-    }
-
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/TextCardView.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/TextCardView.java
deleted file mode 100644
index 8f94e2a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/TextCardView.java
+++ /dev/null
@@ -1,56 +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.supportleanbackshowcase.cards;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.BaseCardView;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
-import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-public class TextCardView extends BaseCardView {
-
-    public TextCardView(Context context) {
-        super(context, null, R.style.TextCardStyle);
-        LayoutInflater.from(getContext()).inflate(R.layout.text_icon_card, this);
-        setFocusable(true);
-    }
-
-    public void updateUi(Card card) {
-        TextView extraText = findViewById(R.id.extra_text);
-        TextView primaryText = findViewById(R.id.primary_text);
-        final ImageView imageView = findViewById(R.id.main_image);
-
-        extraText.setText(card.getExtraText());
-        primaryText.setText(card.getTitle());
-
-        // Create a rounded drawable.
-        int resourceId = card.getLocalImageResourceId(getContext());
-        Bitmap bitmap = BitmapFactory
-                .decodeResource(getContext().getResources(), resourceId);
-        RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(getContext().getResources(), bitmap);
-        drawable.setAntiAlias(true);
-        drawable.setCornerRadius(
-                Math.max(bitmap.getWidth(), bitmap.getHeight()) / 2.0f);
-        imageView.setImageDrawable(drawable);
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/AbstractCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/AbstractCardPresenter.java
deleted file mode 100644
index fe7541b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/AbstractCardPresenter.java
+++ /dev/null
@@ -1,79 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.BaseCardView;
-import android.support.v17.leanback.widget.Presenter;
-import android.view.ViewGroup;
-
-/**
- * This abstract, generic class will create and manage the
- * ViewHolder and will provide typed Presenter callbacks such that you do not have to perform casts
- * on your own.
- *
- * @param <T> View type for the card.
- */
-public abstract class AbstractCardPresenter<T extends BaseCardView> extends Presenter {
-
-    private static final String TAG = "AbstractCardPresenter";
-    private final Context mContext;
-
-    /**
-     * @param context The current context.
-     */
-    public AbstractCardPresenter(Context context) {
-        mContext = context;
-    }
-
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override public final ViewHolder onCreateViewHolder(ViewGroup parent) {
-        T cardView = onCreateView();
-        return new ViewHolder(cardView);
-    }
-
-    @Override public final void onBindViewHolder(ViewHolder viewHolder, Object item) {
-        Card card = (Card) item;
-        onBindViewHolder(card, (T) viewHolder.view);
-    }
-
-    @Override public final void onUnbindViewHolder(ViewHolder viewHolder) {
-        onUnbindViewHolder((T) viewHolder.view);
-    }
-
-    public void onUnbindViewHolder(T cardView) {
-        // Nothing to clean up. Override if necessary.
-    }
-
-    /**
-     * Invoked when a new view is created.
-     *
-     * @return Returns the newly created view.
-     */
-    protected abstract T onCreateView();
-
-    /**
-     * Implement this method to update your card's view with the data bound to it.
-     *
-     * @param card The model containing the data for the card.
-     * @param cardView The view the card is bound to.
-     * @see Presenter#onBindViewHolder(Presenter.ViewHolder, Object)
-     */
-    public abstract void onBindViewHolder(Card card, T cardView);
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CardPresenterSelector.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CardPresenterSelector.java
deleted file mode 100644
index d4e8283..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CardPresenterSelector.java
+++ /dev/null
@@ -1,92 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-
-import java.util.HashMap;
-
-/**
- * This PresenterSelector will decide what Presenter to use depending on a given card's type.
- */
-public class CardPresenterSelector extends PresenterSelector {
-
-    private final Context mContext;
-    private final HashMap<Card.Type, Presenter> presenters = new HashMap<Card.Type, Presenter>();
-
-    public CardPresenterSelector(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public Presenter getPresenter(Object item) {
-        if (!(item instanceof Card)) throw new RuntimeException(
-                String.format("The PresenterSelector only supports data items of type '%s'",
-                        Card.class.getName()));
-        Card card = (Card) item;
-        Presenter presenter = presenters.get(card.getType());
-        if (presenter == null) {
-            switch (card.getType()) {
-                case SINGLE_LINE:
-                    presenter = new SingleLineCardPresenter(mContext);
-                    break;
-                case MOVIE:
-                case MOVIE_BASE:
-                case MOVIE_COMPLETE:
-                case SQUARE_BIG:
-                case GRID_SQUARE:
-                case GAME: {
-                    int themeResId = R.style.MovieCardSimpleTheme;
-                    if (card.getType() == Card.Type.MOVIE_BASE) {
-                        themeResId = R.style.MovieCardBasicTheme;
-                    } else if (card.getType() == Card.Type.MOVIE_COMPLETE) {
-                        themeResId = R.style.MovieCardCompleteTheme;
-                    } else if (card.getType() == Card.Type.SQUARE_BIG) {
-                        themeResId = R.style.SquareBigCardTheme;
-                    } else if (card.getType() == Card.Type.GRID_SQUARE) {
-                        themeResId = R.style.GridCardTheme;
-                    } else if (card.getType() == Card.Type.GAME) {
-                        themeResId = R.style.GameCardTheme;
-                    }
-                    presenter = new ImageCardViewPresenter(mContext, themeResId);
-                    break;
-                }
-                case SIDE_INFO:
-                    presenter = new SideInfoCardPresenter(mContext);
-                    break;
-                case TEXT:
-                    presenter = new TextCardPresenter(mContext);
-                    break;
-                case ICON:
-                    presenter = new IconCardPresenter(mContext);
-                    break;
-                case CHARACTER:
-                    presenter = new CharacterCardPresenter(mContext);
-                    break;
-                default:
-                    presenter = new ImageCardViewPresenter(mContext);
-                    break;
-            }
-        }
-        presenters.put(card.getType(), presenter);
-        return presenter;
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CharacterCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CharacterCardPresenter.java
deleted file mode 100644
index 35a42a9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/CharacterCardPresenter.java
+++ /dev/null
@@ -1,40 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.cards.CharacterCardView;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-
-/**
- * This Presenter is used to display the characters card row in the DetailView examples.
- */
-public class CharacterCardPresenter extends AbstractCardPresenter<CharacterCardView> {
-
-    public CharacterCardPresenter(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected CharacterCardView onCreateView() {
-        return new CharacterCardView(getContext());
-    }
-
-    @Override
-    public void onBindViewHolder(Card card, CharacterCardView cardView) {
-        cardView.updateUi(card);
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/IconCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/IconCardPresenter.java
deleted file mode 100644
index c628988..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/IconCardPresenter.java
+++ /dev/null
@@ -1,59 +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.supportleanbackshowcase.cards.presenters;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.view.View;
-import android.widget.ImageView;
-
-/**
- * This Presenter will display cards which consists of a single icon which will be highlighted by a
- * surrounding circle when the card is focused. AndroidTV uses these cards for entering settings
- * menu.
- */
-public class IconCardPresenter extends ImageCardViewPresenter {
-    private static final int ANIMATION_DURATION = 200;
-
-    public IconCardPresenter(Context context) {
-        super(context, R.style.IconCardTheme);
-    }
-
-    @Override
-    protected ImageCardView onCreateView() {
-        final ImageCardView imageCardView = super.onCreateView();
-        final ImageView image = imageCardView.getMainImageView();
-        image.setBackgroundResource(R.drawable.icon_focused);
-        image.getBackground().setAlpha(0);
-        imageCardView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                animateIconBackground(image.getBackground(), hasFocus);
-            }
-        });
-        return imageCardView;
-    }
-
-    private void animateIconBackground(Drawable drawable, boolean hasFocus) {
-        if (hasFocus) {
-            ObjectAnimator.ofInt(drawable, "alpha", 0, 255).setDuration(ANIMATION_DURATION).start();
-        } else {
-            ObjectAnimator.ofInt(drawable, "alpha", 255, 0).setDuration(ANIMATION_DURATION).start();
-        }
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/ImageCardViewPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/ImageCardViewPresenter.java
deleted file mode 100644
index f0b75f1..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/ImageCardViewPresenter.java
+++ /dev/null
@@ -1,67 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.view.ContextThemeWrapper;
-
-import android.view.View;
-import android.widget.Toast;
-import com.squareup.picasso.Picasso;
-
-/**
- * A very basic {@link ImageCardView} {@link android.support.v17.leanback.widget.Presenter}.You can
- * pass a custom style for the ImageCardView in the constructor. Use the default constructor to
- * create a Presenter with a default ImageCardView style.
- */
-public class ImageCardViewPresenter extends AbstractCardPresenter<ImageCardView> {
-
-    public ImageCardViewPresenter(Context context, int cardThemeResId) {
-        super(new ContextThemeWrapper(context, cardThemeResId));
-    }
-
-    public ImageCardViewPresenter(Context context) {
-        this(context, R.style.DefaultCardTheme);
-    }
-
-    @Override
-    protected ImageCardView onCreateView() {
-        ImageCardView imageCardView = new ImageCardView(getContext());
-        imageCardView.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Toast.makeText(getContext(), "Clicked on ImageCardView", Toast.LENGTH_SHORT).show();
-            }
-        });
-        return imageCardView;
-    }
-
-    @Override
-    public void onBindViewHolder(Card card, final ImageCardView cardView) {
-        cardView.setTag(card);
-        cardView.setTitleText(card.getTitle());
-        cardView.setContentText(card.getDescription());
-        if (card.getLocalImageResourceName() != null) {
-            int resourceId = getContext().getResources()
-                    .getIdentifier(card.getLocalImageResourceName(),
-                            "drawable", getContext().getPackageName());
-            Picasso.with(getContext()).load(resourceId).into(cardView.getMainImageView());
-        }
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SideInfoCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SideInfoCardPresenter.java
deleted file mode 100644
index b9f44fc..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SideInfoCardPresenter.java
+++ /dev/null
@@ -1,74 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.BaseCardView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.squareup.picasso.Picasso;
-
-/**
- * This Presenter will display a card consisting of an image on the left side of the card followed
- * by text on the right side. The image and text have equal width. The text will work like a info
- * box, thus it will be hidden if the parent row is inactive. This behavior is unique to this card
- * and requires a special focus handler.
- */
-public class SideInfoCardPresenter extends AbstractCardPresenter<BaseCardView> {
-
-    public SideInfoCardPresenter(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected BaseCardView onCreateView() {
-        final BaseCardView cardView = new BaseCardView(getContext(), null,
-                R.style.SideInfoCardStyle);
-        cardView.setFocusable(true);
-        cardView.addView(LayoutInflater.from(getContext()).inflate(R.layout.side_info_card, null));
-        return cardView;
-    }
-
-    @Override
-    public void onBindViewHolder(Card card, BaseCardView cardView) {
-        ImageView imageView = (ImageView) cardView.findViewById(R.id.main_image);
-        if (card.getLocalImageResourceName() != null) {
-            int width = (int) getContext().getResources()
-                    .getDimension(R.dimen.sidetext_image_card_width);
-            int height = (int) getContext().getResources()
-                    .getDimension(R.dimen.sidetext_image_card_height);
-            int resourceId = getContext().getResources()
-                    .getIdentifier(card.getLocalImageResourceName(),
-                            "drawable", getContext().getPackageName());
-            Picasso.with(getContext()).load(resourceId).resize(width, height).centerCrop()
-                    .into(imageView);
-        }
-
-        TextView primaryText = (TextView) cardView.findViewById(R.id.primary_text);
-        primaryText.setText(card.getTitle());
-
-        TextView secondaryText = (TextView) cardView.findViewById(R.id.secondary_text);
-        secondaryText.setText(card.getDescription());
-
-        TextView extraText = (TextView) cardView.findViewById(R.id.extra_text);
-        extraText.setText(card.getExtraText());
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SingleLineCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SingleLineCardPresenter.java
deleted file mode 100644
index a823993..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/SingleLineCardPresenter.java
+++ /dev/null
@@ -1,38 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-import android.support.v17.leanback.widget.ImageCardView;
-
-/**
- * This Presenter will display a card which consists of a big image followed by a colored footer.
- * Not only the colored footer is unique to this card, but also it's footer (info) will be visible
- * even when its parent row is inactive.
- */
-public class SingleLineCardPresenter extends ImageCardViewPresenter {
-
-    public SingleLineCardPresenter(Context context) {
-        super(context, R.style.SingleLineCardTheme);
-    }
-
-    @Override public void onBindViewHolder(Card card, ImageCardView cardView) {
-        super.onBindViewHolder(card, cardView);
-        cardView.setInfoAreaBackgroundColor(card.getFooterColor());
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/TextCardPresenter.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/TextCardPresenter.java
deleted file mode 100644
index 7e65f0f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/cards/presenters/TextCardPresenter.java
+++ /dev/null
@@ -1,41 +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.supportleanbackshowcase.cards.presenters;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.cards.TextCardView;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-
-/**
- * The Presenter displays a card consisting of text as a replacement for a big image. The footer is
- * also quite unique since it does contain two images rather than one or non.
- */
-public class TextCardPresenter extends AbstractCardPresenter<TextCardView> {
-
-    public TextCardPresenter(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected TextCardView onCreateView() {
-        return new TextCardView(getContext());
-    }
-
-    @Override
-    public void onBindViewHolder(Card card, TextCardView cardView) {
-        cardView.updateUi(card);
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Card.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Card.java
deleted file mode 100644
index ccaaf72..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Card.java
+++ /dev/null
@@ -1,129 +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.supportleanbackshowcase.models;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.util.Log;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * This is a generic example of a custom data object, containing info we might want to keep with
- * each card on the home screen
- */
-public class Card {
-
-    @SerializedName("title") private String mTitle = "";
-    @SerializedName("description") private String mDescription = "";
-    @SerializedName("extraText") private String mExtraText = "";
-    @SerializedName("imageUrl") private String mImageUrl;
-    @SerializedName("footerColor") private String mFooterColor = null;
-    @SerializedName("selectedColor") private String mSelectedColor = null;
-    @SerializedName("localImageResource") private String mLocalImageResource = null;
-    @SerializedName("footerIconLocalImageResource") private String mFooterResource = null;
-    @SerializedName("type") private Card.Type mType;
-    @SerializedName("id") private int mId;
-    @SerializedName("width") private int mWidth;
-    @SerializedName("height") private int mHeight;
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public int getWidth() {
-        return mWidth;
-    }
-
-    public int getHeight() {
-        return mHeight;
-    }
-
-    public int getId() {
-        return mId;
-    }
-
-    public Card.Type getType() {
-        return mType;
-    }
-
-    public String getDescription() {
-        return mDescription;
-    }
-
-    public String getExtraText() {
-        return mExtraText;
-    }
-
-    public int getFooterColor() {
-        if (mFooterColor == null) return -1;
-        return Color.parseColor(mFooterColor);
-    }
-
-    public int getSelectedColor() {
-        if (mSelectedColor == null) return -1;
-        return Color.parseColor(mSelectedColor);
-    }
-
-    public String getImageUrl() {
-        return mImageUrl;
-    }
-
-    public URI getImageURI() {
-        if (getImageUrl() == null) return null;
-        try {
-            return new URI(getImageUrl());
-        } catch (URISyntaxException e) {
-            Log.d("URI exception: ", getImageUrl());
-            return null;
-        }
-    }
-
-    public int getLocalImageResourceId(Context context) {
-        return context.getResources().getIdentifier(getLocalImageResourceName(), "drawable",
-                                                    context.getPackageName());
-    }
-
-    public String getLocalImageResourceName() {
-        return mLocalImageResource;
-    }
-
-    public String getFooterLocalImageResourceName() {
-        return mFooterResource;
-    }
-
-    public enum Type {
-
-        MOVIE_COMPLETE,
-        MOVIE,
-        MOVIE_BASE,
-        ICON,
-        SQUARE_BIG,
-        SINGLE_LINE,
-        GAME,
-        SQUARE_SMALL,
-        DEFAULT,
-        SIDE_INFO,
-        SIDE_INFO_TEST_1,
-        TEXT,
-        CHARACTER,
-        GRID_SQUARE
-
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/CardRow.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/CardRow.java
deleted file mode 100644
index ffc5001..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/CardRow.java
+++ /dev/null
@@ -1,56 +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.supportleanbackshowcase.models;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.List;
-
-/**
- * This class represents a row of cards. In a real world application you might want to store more
- * data than in this example.
- */
-public class CardRow {
-
-    // default is a list of cards
-    public static final int TYPE_DEFAULT = 0;
-    // section header
-    public static final int TYPE_SECTION_HEADER = 1;
-    // divider
-    public static final int TYPE_DIVIDER = 2;
-
-    @SerializedName("type") private int mType = TYPE_DEFAULT;
-    // Used to determine whether the row shall use shadows when displaying its cards or not.
-    @SerializedName("shadow") private boolean mShadow = true;
-    @SerializedName("title") private String mTitle;
-    @SerializedName("cards") private List<Card> mCards;
-
-    public int getType() {
-        return mType;
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public boolean useShadow() {
-        return mShadow;
-    }
-
-    public List<Card> getCards() {
-        return mCards;
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/DetailedCard.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/DetailedCard.java
deleted file mode 100644
index 8727a40..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/DetailedCard.java
+++ /dev/null
@@ -1,71 +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.supportleanbackshowcase.models;
-
-import android.content.Context;
-import android.support.v17.leanback.supportleanbackshowcase.models.Card;
-
-import com.google.gson.annotations.SerializedName;
-
-public class DetailedCard {
-
-    @SerializedName("title") private String mTitle = "";
-    @SerializedName("description") private String mDescription = "";
-    @SerializedName("text") private String mText = "";
-    @SerializedName("localImageResource") private String mLocalImageResource = null;
-    @SerializedName("price") private String mPrice = null;
-    @SerializedName("characters") private Card[] mCharacters = null;
-    @SerializedName("recommended") private Card[] mRecommended = null;
-    @SerializedName("year") private int mYear = 0;
-
-
-    public String getPrice() {
-        return mPrice;
-    }
-
-    public int getYear() {
-        return mYear;
-    }
-
-    public String getLocalImageResource() {
-        return mLocalImageResource;
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    public String getDescription() {
-        return mDescription;
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public Card[] getCharacters() {
-        return mCharacters;
-    }
-
-    public Card[] getRecommended() {
-        return mRecommended;
-    }
-
-    public int getLocalImageResourceId(Context context) {
-        return context.getResources()
-                      .getIdentifier(getLocalImageResource(), "drawable", context.getPackageName());
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Movie.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Movie.java
deleted file mode 100644
index 83912b3..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Movie.java
+++ /dev/null
@@ -1,50 +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.supportleanbackshowcase.models;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.io.Serializable;
-
-public class Movie implements Serializable {
-
-    private static final long serialVersionUID = 133742L;
-
-    @SerializedName("title")
-    private String mTitle = "";
-    @SerializedName("price_hd")
-    private String mPriceHd = "n/a";
-    @SerializedName("price_sd")
-    private String mPriceSd = "n/a";
-    @SerializedName("breadcrump")
-    private String mBreadcrump = "";
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public String getBreadcrump() {
-        return mBreadcrump;
-    }
-
-    public String getPriceHd() {
-        return mPriceHd;
-    }
-
-    public String getPriceSd() {
-        return mPriceSd;
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java
deleted file mode 100644
index 1ba29ca..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/Song.java
+++ /dev/null
@@ -1,111 +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.supportleanbackshowcase.models;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.supportleanbackshowcase.R;
-import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
-import android.support.v17.leanback.widget.MultiActionsProvider;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.google.gson.Gson;
-import com.google.gson.annotations.SerializedName;
-
-public class Song implements MultiActionsProvider {
-
-    @SerializedName("title") private String mTitle = "";
-    @SerializedName("description") private String mDescription = "";
-    @SerializedName("text") private String mText = "";
-    @SerializedName("image") private String mImage = null;
-    @SerializedName("file") private String mFile = null;
-    @SerializedName("duration") private String mDuration = null;
-    @SerializedName("number") private int mNumber = 0;
-    @SerializedName("favorite") private boolean mFavorite = false;
-
-    private MultiAction[] mMediaRowActions;
-
-
-    public void setMediaRowActions(MultiAction[] mediaRowActions) {
-        mMediaRowActions = mediaRowActions;
-    }
-
-    public MultiAction[] getMediaRowActions() {
-        return mMediaRowActions;
-    }
-
-    public String getDuration() {
-        return mDuration;
-    }
-
-    public void setDuration(String duration) {
-        mDuration = duration;
-    }
-
-    public int getNumber() {
-        return mNumber;
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    public String getDescription() {
-        return mDescription;
-    }
-
-    public void setDescription(String description) {
-        mDescription = description;
-    }
-
-    public String getTitle() {
-        return mTitle;
-    }
-
-    public void setTitle(String title) {
-        mTitle = title;
-    }
-
-    public boolean isFavorite() {
-        return mFavorite;
-    }
-
-    public void setFavorite(boolean favorite) {
-        mFavorite = favorite;
-    }
-
-    public int getFileResource(Context context) {
-        return context.getResources()
-                      .getIdentifier(mFile, "raw", context.getPackageName());
-    }
-
-    public int getImageResource(Context context) {
-        return context.getResources()
-                      .getIdentifier(mImage, "drawable", context.getPackageName());
-    }
-
-    @Override
-    public MultiAction[] getActions() {
-        return mMediaRowActions;
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/SongList.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/SongList.java
deleted file mode 100644
index 034ae19..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/models/SongList.java
+++ /dev/null
@@ -1,31 +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.supportleanbackshowcase.models;
-
-import com.google.gson.annotations.SerializedName;
-
-import java.util.List;
-
-/**
- */
-public class SongList {
-
-    @SerializedName("songs") private List<Song> mSongs;
-
-    public List<Song> getSongs() {
-        return mSongs;
-    }
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/CardListRow.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/CardListRow.java
deleted file mode 100644
index 19f2437..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/CardListRow.java
+++ /dev/null
@@ -1,45 +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.supportleanbackshowcase.utils;
-
-import android.support.v17.leanback.supportleanbackshowcase.app.details.ShadowRowPresenterSelector;
-import android.support.v17.leanback.supportleanbackshowcase.models.CardRow;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ObjectAdapter;
-
-/**
- * The {@link CardListRow} allows the {@link ShadowRowPresenterSelector} to access the {@link CardRow}
- * held by the row and determine whether to use a {@link android.support.v17.leanback.widget.Presenter}
- * with or without a shadow.
- */
-public class CardListRow extends ListRow {
-
-    private CardRow mCardRow;
-
-    public CardListRow(HeaderItem header, ObjectAdapter adapter, CardRow cardRow) {
-        super(header, adapter);
-        setCardRow(cardRow);
-    }
-
-    public CardRow getCardRow() {
-        return mCardRow;
-    }
-
-    public void setCardRow(CardRow cardRow) {
-        this.mCardRow = cardRow;
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Constants.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Constants.java
deleted file mode 100644
index 1c9dd9c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Constants.java
+++ /dev/null
@@ -1,17 +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.supportleanbackshowcase.utils;
-
-public class Constants {
-
-    public static final boolean LOCAL_LOGD = true;
-
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/PicassoBackgroundManagerTarget.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/PicassoBackgroundManagerTarget.java
deleted file mode 100644
index 2723d0b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/PicassoBackgroundManagerTarget.java
+++ /dev/null
@@ -1,61 +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.supportleanbackshowcase.utils;
-
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.app.BackgroundManager;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-/**
- * Picasso target for updating default_background images
- */
-public class PicassoBackgroundManagerTarget implements Target {
-
-    private BackgroundManager mBackgroundManager;
-
-    public PicassoBackgroundManagerTarget(BackgroundManager backgroundManager) {
-        this.mBackgroundManager = backgroundManager;
-    }
-
-    @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
-        this.mBackgroundManager.setBitmap(bitmap);
-    }
-
-    @Override public void onBitmapFailed(Drawable drawable) {
-        this.mBackgroundManager.setDrawable(drawable);
-    }
-
-    @Override public void onPrepareLoad(Drawable drawable) {
-        // Do nothing, default_background manager has its own transitions
-    }
-
-    @Override public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        PicassoBackgroundManagerTarget that = (PicassoBackgroundManagerTarget) o;
-
-        if (!mBackgroundManager.equals(that.mBackgroundManager)) return false;
-
-        return true;
-    }
-
-    @Override public int hashCode() {
-        return mBackgroundManager.hashCode();
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/ResourceCache.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/ResourceCache.java
deleted file mode 100644
index 09a1965..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/ResourceCache.java
+++ /dev/null
@@ -1,36 +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.supportleanbackshowcase.utils;
-
-import android.util.SparseArray;
-import android.view.View;
-
-/**
- * ResourceCache allows retrieving children from a given view and caches the resulting views in
- * order to prevent future lookups.
- */
-public class ResourceCache {
-
-    private final SparseArray<View> mCachedViews = new SparseArray<View>();
-
-    public <ViewType extends View> ViewType getViewById(View view, int resId) {
-        View child = mCachedViews.get(resId, null);
-        if (child == null) {
-            child = view.findViewById(resId);
-            mCachedViews.put(resId, child);
-        }
-        return (ViewType) child;
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Utils.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Utils.java
deleted file mode 100644
index 4416cbe..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/utils/Utils.java
+++ /dev/null
@@ -1,58 +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.supportleanbackshowcase.utils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A collection of utility methods, all static.
- */
-public class Utils {
-
-    public static int convertDpToPixel(Context ctx, int dp) {
-        float density = ctx.getResources().getDisplayMetrics().density;
-        return Math.round((float) dp * density);
-    }
-
-    /**
-     * Will read the content from a given {@link InputStream} and return it as a {@link String}.
-     *
-     * @param inputStream The {@link InputStream} which should be read.
-     * @return Returns <code>null</code> if the the {@link InputStream} could not be read. Else
-     * returns the content of the {@link InputStream} as {@link String}.
-     */
-    public static String inputStreamToString(InputStream inputStream) {
-        try {
-            byte[] bytes = new byte[inputStream.available()];
-            inputStream.read(bytes, 0, bytes.length);
-            String json = new String(bytes);
-            return json;
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
-    public static Uri getResourceUri(Context context, int resID) {
-        return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                                 context.getResources().getResourcePackageName(resID) + '/' +
-                                 context.getResources().getResourceTypeName(resID) + '/' +
-                                 context.getResources().getResourceEntryName(resID));
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-v21/song_row_background_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-v21/song_row_background_focused.xml
deleted file mode 100644
index 34e2bd7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-v21/song_row_background_focused.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item>
-        <color android:color="#384248"></color>
-    </item>
-    <item>
-        <ripple xmlns:android="http://schemas.android.com/apk/res/android"
-                android:color="?android:attr/colorControlHighlight">
-            <item android:id="@android:id/mask">
-                <shape android:shape="rectangle">
-                    <corners android:radius="2dp"/>
-                    <solid android:color="#FFFFFF"/>
-                </shape>
-            </item>
-
-        </ripple>
-    </item>
-
-</layer-list>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_canyon.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_canyon.jpg
deleted file mode 100644
index 0f77261..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_canyon.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_food.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_food.jpg
deleted file mode 100644
index cbe05f8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_food.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_sax.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_sax.jpg
deleted file mode 100644
index 20fec8c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/background_sax.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_dummy_16_9_l.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_dummy_16_9_l.png
deleted file mode 100644
index 6088150..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_dummy_16_9_l.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_01.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_01.jpg
deleted file mode 100644
index df42850..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_01.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_02.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_02.jpg
deleted file mode 100644
index f633b04..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_02.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_03.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_03.jpg
deleted file mode 100644
index 52a6b01..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_03.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_04.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_04.jpg
deleted file mode 100644
index 428e6bf..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_04.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_05.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_05.jpg
deleted file mode 100644
index a816fe8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_05.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_06.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_06.jpg
deleted file mode 100644
index 8746fd9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_06.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_07.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_07.jpg
deleted file mode 100644
index 770f22e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_07.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_08.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_08.jpg
deleted file mode 100644
index cf83f9f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_08.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_09.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_09.jpg
deleted file mode 100644
index 2622893..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_movie_09.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_01.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_01.jpg
deleted file mode 100644
index d604a50..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_01.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_02.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_02.jpg
deleted file mode 100644
index cdcee88..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_02.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_03.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_03.jpg
deleted file mode 100644
index 229f0c4..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_03.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_04.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_04.jpg
deleted file mode 100644
index b7bbe9c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_04.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_05.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_05.jpg
deleted file mode 100644
index bfa1389..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_05.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_06.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_06.jpg
deleted file mode 100644
index 033d60e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_06.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_07.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_07.jpg
deleted file mode 100644
index 784d0e9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_07.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_08.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_08.jpg
deleted file mode 100644
index 040e222..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_08.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_09.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_09.jpg
deleted file mode 100644
index 2a9f136..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/card_image_music_09.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_action.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_action.png
deleted file mode 100755
index f3cb7af..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_action.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_animation.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_animation.png
deleted file mode 100755
index a1d9f40..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_animation.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_classics.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_classics.png
deleted file mode 100755
index 17c1580..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_classics.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_comedy.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_comedy.png
deleted file mode 100755
index 5a23e6e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_comedy.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_crime.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_crime.png
deleted file mode 100755
index 52450a6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_crime.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_documentary.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_documentary.png
deleted file mode 100755
index afb2b61..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_documentary.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_drama.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_drama.png
deleted file mode 100755
index 79e1b57..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/category_drama.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_01.png
deleted file mode 100755
index 9fb73f7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_02.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_02.png
deleted file mode 100755
index 9e0abfe..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_02.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_03.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_03.png
deleted file mode 100755
index cd78b71..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_03.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_04.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_04.png
deleted file mode 100755
index bf7561c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_04.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_05.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_05.png
deleted file mode 100755
index eae5338..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_05.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_06.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_06.png
deleted file mode 100755
index b1feda0..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_06.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_07.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_07.png
deleted file mode 100755
index 0f7dd21..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_07.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_08.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_08.png
deleted file mode 100755
index b904137..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/coffee_and_tea_08.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_01.png
deleted file mode 100755
index b4757a8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_02.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_02.png
deleted file mode 100755
index 0d746d3..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_02.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_03.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_03.png
deleted file mode 100755
index ddde1ef..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_03.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_04.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_04.png
deleted file mode 100755
index ca09156..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_04.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_05.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_05.png
deleted file mode 100755
index 8c4d7e6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_05.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_06.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_06.png
deleted file mode 100755
index d81ad8c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_06.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_07.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_07.png
deleted file mode 100755
index a0f381d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_07.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_08.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_08.png
deleted file mode 100755
index c377d27..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_08.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_09.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_09.png
deleted file mode 100755
index de5a207..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_09.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_10.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_10.png
deleted file mode 100755
index 958f47e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_10.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_11.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_11.png
deleted file mode 100755
index 36a8007..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_11.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_12.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_12.png
deleted file mode 100755
index 4d60e8e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_12.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_13.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_13.png
deleted file mode 100755
index b4a8837..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_13.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_14.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_14.png
deleted file mode 100755
index ae3d40b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/food_14.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_crazy_one.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_crazy_one.jpg
deleted file mode 100644
index 9bb47c1..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_crazy_one.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_cursed.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_cursed.jpg
deleted file mode 100644
index 7d4b45a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_cursed.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_fairy.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_fairy.jpg
deleted file mode 100644
index c1658e9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_fairy.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_hear_the_roar.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_hear_the_roar.jpg
deleted file mode 100644
index 78003b2..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_hear_the_roar.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_silence.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_silence.jpg
deleted file mode 100644
index 1672536..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/game_silence.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg
deleted file mode 100644
index 447de09..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/google_map.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_cc.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_cc.png
deleted file mode 100644
index 4615394..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_cc.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_installed.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_installed.png
deleted file mode 100644
index 190f6ba..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_installed.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_apps.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_apps.png
deleted file mode 100755
index ed92603..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_apps.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_more.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_more.png
deleted file mode 100755
index 5307576..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_more.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_parental_control.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_parental_control.png
deleted file mode 100755
index d541c6a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_parental_control.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_settings.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_settings.png
deleted file mode 100755
index f2eaa45..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_settings.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_time.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_time.png
deleted file mode 100755
index 72899ae..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_time.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_3_bar.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_3_bar.png
deleted file mode 100755
index f9abb6c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_3_bar.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_4_bar.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_4_bar.png
deleted file mode 100755
index dcd2088..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_settings_wifi_4_bar.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_off.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_off.png
deleted file mode 100644
index cbee75a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_off.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_on_yellow.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_on_yellow.png
deleted file mode 100644
index 28cd07a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/ic_star_on_yellow.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/movie_poster_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/movie_poster_01.png
deleted file mode 100644
index e09af32..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/movie_poster_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_red.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_red.png
deleted file mode 100644
index e4c6056..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_red.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_white.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_white.png
deleted file mode 100644
index 83e7c8b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/stars_white.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_browse.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_browse.png
deleted file mode 100755
index 374f357..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_browse.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_cards.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_cards.png
deleted file mode 100755
index 1ddadfa..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_cards.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_custom_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_custom_01.png
deleted file mode 100755
index f73b9b7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_custom_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_detail.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_detail.png
deleted file mode 100755
index aa881b8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_detail.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_dialog.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_dialog.png
deleted file mode 100755
index 6508d08..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_dialog.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_grid.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_grid.png
deleted file mode 100755
index 686566f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_grid.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_music_consumption.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_music_consumption.png
deleted file mode 100755
index ffc653d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_music_consumption.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_settings.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_settings.png
deleted file mode 100755
index 8337b92..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_settings.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_video_consumption.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_video_consumption.png
deleted file mode 100755
index a0a9d95..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_video_consumption.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_wizard.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_wizard.png
deleted file mode 100755
index 4338d30..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/thumbnail_example_wizard.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/title_android_tv.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/title_android_tv.png
deleted file mode 100644
index 8d1e241..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable-xhdpi/title_android_tv.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/app_icon_your_company.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/app_icon_your_company.png
deleted file mode 100644
index 0a47b01..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/app_icon_your_company.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room.jpg
deleted file mode 100644
index 47e8851..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room_wide.jpg b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room_wide.jpg
deleted file mode 100644
index 156d55c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/bg_living_room_wide.jpg
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_focused.xml
deleted file mode 100644
index 5c2570d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_focused.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <padding
-        android:bottom="4dp"
-        android:left="4dp"
-        android:right="4dp"
-        android:top="4dp"></padding>
-    <solid android:color="#FFEEEEEE"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused.xml
deleted file mode 100644
index db4cf9c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <padding
-        android:bottom="4dp"
-        android:left="4dp"
-        android:right="4dp"
-        android:top="4dp"></padding>
-    <solid android:color="#FFCCCCCC"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused_padding.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused_padding.xml
deleted file mode 100644
index 8da3812..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/character_not_focused_padding.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <padding
-        android:bottom="4dp"
-        android:left="4dp"
-        android:right="4dp"
-        android:top="4dp"></padding>
-    <solid android:color="#00CCCCCC"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/default_background.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/default_background.xml
deleted file mode 100644
index d9fa80b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/default_background.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <gradient
-        android:angle="-270"
-        android:endColor="@color/background_gradient_end"
-        android:startColor="@color/background_gradient_start"/>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_01.png
deleted file mode 100755
index d5b2900..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_02.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_02.png
deleted file mode 100755
index ced0a7c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_02.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_03.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_03.png
deleted file mode 100755
index 276cacf..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_03.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_04.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_04.png
deleted file mode 100755
index e90487d..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_04.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_05.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_05.png
deleted file mode 100755
index f048ecb..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_05.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_06.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_06.png
deleted file mode 100755
index f4d15eb..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_06.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_07.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_07.png
deleted file mode 100755
index 7ac0af3..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_07.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_08.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_08.png
deleted file mode 100755
index e6fc51e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/face_08.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml
deleted file mode 100644
index 03e16ad..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml
deleted file mode 100644
index bce1999..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_favorite_filled_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"
-        android:fillColor="@color/song_row_favorite_color"/>
-</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml
deleted file mode 100644
index ac5d3f3..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_filled_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"
-        android:fillColor="@color/song_row_favorite_color"/>
-</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml
deleted file mode 100644
index 4147e81..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_01.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_01.png
deleted file mode 100644
index 6fdb583..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_01.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_02.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_02.png
deleted file mode 100644
index 8728c6f..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_02.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_03.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_03.png
deleted file mode 100644
index 2364de9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_03.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_04.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_04.png
deleted file mode 100644
index 9c4fa52..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_04.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_05.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_05.png
deleted file mode 100644
index c56828c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_05.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_06.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_06.png
deleted file mode 100644
index b9f34f4..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_06.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_07.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_07.png
deleted file mode 100644
index 76ea4f6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_07.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_08.png b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_08.png
deleted file mode 100644
index 4b1ddb9..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_08.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_focused.xml
deleted file mode 100644
index bab1cc6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/icon_focused.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="oval">
-    <solid android:color="#4DEEEEEE"></solid>
-    <size
-        android:width="96dp"
-        android:height="96dp"/>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/overlay_black.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/overlay_black.xml
deleted file mode 100644
index 9fccc24..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/overlay_black.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="#E6000000"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background.xml
deleted file mode 100644
index 0329874e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/song_row_background_focused" android:state_focused="true"></item>
-    <item>
-        <color android:color="#384248"></color>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background_focused.xml
deleted file mode 100644
index 152e7e6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/song_row_background_focused.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item>
-        <color android:color="#384248"></color>
-    </item>
-    <item>
-        <color android:color="#1AFFFFFF"></color>
-    </item>
-</layer-list>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_background_blackned.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_background_blackned.xml
deleted file mode 100644
index bea8d66..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_background_blackned.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-     <item android:drawable="@drawable/background_canyon"></item>
-     <item android:drawable="@drawable/overlay_black"></item>
-</layer-list>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background.xml
deleted file mode 100644
index b4fd39e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@drawable/wizard_important_action_item_background_focused" android:state_focused="true"></item>
-    <item android:drawable="@drawable/wizard_important_action_item_background_not_focused"></item>
-</selector>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_focused.xml
deleted file mode 100644
index d6a1023..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_focused.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#FFFFFF"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_not_focused.xml b/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_not_focused.xml
deleted file mode 100644
index 74cccd0..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/drawable/wizard_important_action_item_background_not_focused.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#B2FFFFFF"></solid>
-</shape>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_cards_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_cards_example.xml
deleted file mode 100644
index 45b5505..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_cards_example.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-
-    <fragment
-        android:id="@+id/cardsFragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.cards.CardExampleFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"></fragment>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_detail_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_detail_example.xml
deleted file mode 100644
index 1dd92d8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_detail_example.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-    <fragment
-        android:id="@+id/detailsFragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.details.DetailViewExampleFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"></fragment>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_grid_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_grid_example.xml
deleted file mode 100644
index f3fcdea..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_grid_example.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-
-    <fragment
-        android:id="@+id/cardsFragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.grid.GridExampleFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"></fragment>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_main.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 9d2eda6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout
-    android:id="@+id/fragmentContainer"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:deviceIds="tv"
-    tools:ignore="MergeRootFrame">
-</RelativeLayout>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_music_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_music_example.xml
deleted file mode 100644
index 9eef61a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_music_example.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-
-    <fragment
-        android:id="@+id/musicFragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.media.MusicConsumptionExampleFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"></fragment>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_settings_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_settings_example.xml
deleted file mode 100644
index c6c81a3..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_settings_example.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent">
-    <fragment
-        android:id="@+id/settingsFragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.settings.SettingsExampleFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"></fragment>
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_video_example.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_video_example.xml
deleted file mode 100644
index 45b12b5..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/activity_video_example.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:id="@+id/videoFragment">
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/character_card.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/character_card.xml
deleted file mode 100644
index dee09a8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/character_card.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             xmlns:lb="http://schemas.android.com/apk/res-auto"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             lb:cardBackground="@null"
-             lb:layout_viewType="main">
-
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/container"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:background="@drawable/character_not_focused_padding">
-
-            <ImageView
-                android:id="@+id/main_image"
-                android:layout_width="@dimen/character_image_card_width"
-                android:layout_height="@dimen/character_image_card_height"
-                android:background="@drawable/character_not_focused"
-                android:src="@drawable/face_08"/>
-        </FrameLayout>
-
-        <TextView
-            android:id="@+id/primary_text"
-            style="@style/Widget.Leanback.ImageCardView.TitleStyle"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="7dp"
-            android:fontFamily="sans-serif-condensed"
-            android:gravity="center"/>
-    </LinearLayout>
-</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml
deleted file mode 100644
index f3ac0df..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/custom_titleview.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-    <!--<android.support.v17.leanback.widget.SearchOrbView-->
-        <!--android:id="@+id/search_orb"-->
-        <!--android:layout_height="wrap_content"-->
-        <!--android:layout_width="wrap_content"-->
-        <!--android:transitionGroup="true"-->
-        <!--android:layout_gravity="center_vertical|start"-->
-        <!--android:layout_marginStart="56dp" />-->
-
-    <AnalogClock
-            android:id="@+id/clock"
-            android:layout_width="80dp"
-            android:layout_height="80dp"
-            android:padding="6dp"
-            android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true"
-            android:layout_marginEnd="24dp" />
-
-    <ImageView
-            android:id="@+id/title_badge_iv"
-            android:layout_width="wrap_content"
-            android:layout_height="224dp"
-            android:adjustViewBounds="true"
-            android:layout_gravity="center_vertical|end"
-            android:layout_toStartOf="@id/clock"
-            android:src="@null"
-            android:layout_centerVertical="true"
-            android:visibility="gone" />
-
-    <TextView
-            android:id="@+id/title_tv"
-            android:textAppearance="@android:style/TextAppearance.Large"
-            android:visibility="gone"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="24dp"
-            android:layout_centerVertical="true"
-            android:layout_toStartOf="@id/clock" />
-
-</merge>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/detail_view_content.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/detail_view_content.xml
deleted file mode 100644
index 5140ed7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/detail_view_content.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/primary_text"
-        style="@style/Widget.Leanback.DetailsDescriptionTitleStyle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="3dp"
-        android:layout_marginTop="15dp"
-        android:fontFamily="sans-serif-light"
-        android:text="Title Text"/>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="14dp"
-        android:gravity="center_vertical"
-        android:orientation="horizontal">
-
-        <TextView
-            android:id="@+id/secondary_text_first"
-            style="@style/Widget.Leanback.DetailsDescriptionSubtitleStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginRight="16dp"
-            android:fontFamily="sans-serif-condensed"
-            android:text="Secondary Text Area"/>
-
-        <TextView
-            android:id="@+id/secondary_text_second"
-            style="@style/Widget.Leanback.DetailsDescriptionSubtitleStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginRight="16dp"
-            android:fontFamily="sans-serif-condensed"
-            android:text="With Icons"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginRight="16dp"
-            android:src="@drawable/ic_cc"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_star_on_yellow"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_star_on_yellow"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_star_on_yellow"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_star_off"/>
-
-        <ImageView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/ic_star_off"/>
-    </LinearLayout>
-
-    <TextView
-        android:id="@+id/extra_text"
-        style="@style/Widget.Leanback.DetailsDescriptionBodyStyle"
-        android:layout_width="match_parent"
-        android:fontFamily="sans-serif-regular"
-        android:layout_height="wrap_content"
-        android:text="Filmmaker Guillermo del Toro teas up with Legendary Pictures to bring audiences a unique take on the monster film with this sci/fi production."/>
-</LinearLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml
deleted file mode 100644
index 4e67908..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/grid_fragment.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:id="@+id/browse_dummy"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent" >
-
-    <android.support.v17.leanback.widget.BrowseFrameLayout
-            android:id="@+id/grid_frame"
-            android:focusable="true"
-            android:focusableInTouchMode="true"
-            android:descendantFocusability="afterDescendants"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" >
-
-        <FrameLayout
-                android:id="@+id/browse_grid_dock"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent" />
-
-    </android.support.v17.leanback.widget.BrowseFrameLayout>
-</FrameLayout>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_fragment.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_fragment.xml
deleted file mode 100644
index 3419b66..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_fragment.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_list_row.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_list_row.xml
deleted file mode 100644
index 6b3cb6b..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/page_list_row.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-
-    <fragment
-        android:id="@+id/page_list_fragment"
-        android:name="android.support.v17.leanback.supportleanbackshowcase.app.page.PageAndListRowFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/side_info_card.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/side_info_card.xml
deleted file mode 100644
index 725af09..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/side_info_card.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:lb="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal">
-
-    <ImageView
-        android:id="@+id/main_image"
-        android:layout_width="144dp"
-        android:layout_height="144dp"
-        lb:layout_viewType="main"
-        />
-
-    <LinearLayout
-        android:id="@+id/info"
-        android:layout_width="144dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:paddingLeft="11dp"
-        android:paddingRight="11dp"
-        android:paddingTop="7dp"
-        android:background="@color/default_card_footer_background_color">
-
-        <TextView
-            android:id="@+id/primary_text"
-            style="@style/Widget.Leanback.ImageCardView.TitleStyle"
-            android:fontFamily="sans-serif-condensed"
-            android:maxLines="2"
-            android:textSize="16sp"/>
-
-        <TextView
-            android:id="@+id/secondary_text"
-            style="@style/Widget.Leanback.ImageCardView.ContentStyle"
-            android:layout_marginTop="4dp"
-            android:fontFamily="sans-serif-condensed"
-            android:maxLines="1"
-            android:textColor="#EEEEEE"/>
-
-        <TextView
-            android:id="@+id/extra_text"
-            style="@style/Widget.Leanback.ImageCardView.ContentStyle"
-            android:layout_marginTop="6dp"
-            android:fontFamily="sans-serif-condensed"
-            android:maxLines="5"/>
-    </LinearLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/text_icon_card.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/text_icon_card.xml
deleted file mode 100644
index 339549a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/text_icon_card.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:lb="http://schemas.android.com/apk/res-auto"
-       android:layout_width="wrap_content"
-       android:layout_height="wrap_content">
-
-    <TextView
-        android:id="@+id/extra_text"
-        style="@style/Widget.Leanback.ImageCardView.ContentStyle"
-        android:layout_width="256dp"
-        android:layout_height="wrap_content"
-        android:background="@color/default_card_background_color"
-        android:fontFamily="sans-serif-condensed"
-        android:lines="7"
-        android:maxLines="7"
-        android:paddingBottom="14dp"
-        android:paddingLeft="15dp"
-        android:paddingRight="15dp"
-        android:paddingTop="12dp"
-        lb:layout_viewType="main"/>
-
-    <android.support.v17.leanback.widget.NonOverlappingRelativeLayout
-        android:layout_width="256dp"
-        android:layout_height="36dp"
-        android:background="@color/default_card_footer_background_color"
-        android:gravity="left"
-        android:orientation="horizontal"
-        android:paddingBottom="7dp"
-        android:paddingLeft="12dp"
-        android:paddingRight="12dp"
-        android:paddingTop="7dp">
-
-        <ImageView
-            android:id="@+id/main_image"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:layout_centerVertical="true"
-            android:layout_gravity="center_vertical"
-            android:layout_marginRight="8dp"
-            android:adjustViewBounds="true"/>
-
-        <TextView
-            android:id="@+id/primary_text"
-            style="@style/Widget.Leanback.ImageCardView.TitleStyle"
-            android:layout_width="match_parent"
-            android:layout_centerVertical="true"
-            android:layout_gravity="left"
-            android:layout_toRightOf="@+id/main_image"
-            android:fontFamily="sans-serif-condensed"
-            android:maxLines="1"
-            />
-
-        <ImageView
-            android:id="@+id/footer_icon"
-            android:layout_width="wrap_content"
-            android:layout_height="32dp"
-            android:layout_alignParentEnd="true"
-            android:layout_centerVertical="true"
-            android:layout_gravity="center_vertical"
-            android:adjustViewBounds="true"
-            android:src="@drawable/stars_white"/>
-
-    </android.support.v17.leanback.widget.NonOverlappingRelativeLayout>
-</merge>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml
deleted file mode 100644
index a5de787..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/titleview.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v17.leanback.supportleanbackshowcase.app.page.CustomTitleView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/browse_title_group"
-        android:padding="16dp"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/video_surface_fragment.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/video_surface_fragment.xml
deleted file mode 100644
index ff5be69..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/video_surface_fragment.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
-           android:layout_width="match_parent"
-           android:layout_height="match_parent">
-</SurfaceView>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/layout/wizard_progress_action_item.xml b/samples/SupportLeanbackShowcase/app/src/main/res/layout/wizard_progress_action_item.xml
deleted file mode 100644
index 9051f38..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/layout/wizard_progress_action_item.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<android.support.v17.leanback.widget.NonOverlappingLinearLayout
-    style="?attr/guidedActionItemContainerStyle"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ProgressBar
-        android:id="@+id/progressBar"
-        style="?android:attr/progressBarStyle"
-        android:indeterminateTintMode="src_in"
-        android:indeterminateTint="#FFAB91"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginRight="10dp"
-        android:indeterminate="false"/>
-
-    <android.support.v17.leanback.widget.NonOverlappingLinearLayout
-        android:id="@+id/guidedactions_item_content"
-        style="?attr/guidedActionItemContentStyle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content">
-
-        <TextView
-            android:id="@+id/guidedactions_item_title"
-            style="?attr/guidedActionItemTitleStyle"
-            android:text="Processing..."
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-
-    </android.support.v17.leanback.widget.NonOverlappingLinearLayout>
-
-</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bc..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/app_banner_sample_app.png b/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/app_banner_sample_app.png
deleted file mode 100644
index 222c1e5..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/app_banner_sample_app.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72c..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/cards_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/cards_example.json
deleted file mode 100644
index 4fc4672..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/cards_example.json
+++ /dev/null
@@ -1,517 +0,0 @@
-[
-  {
-    "type" : 1,
-    "title": "ImageCardView Examples"
-  },
-  {
-    "title": "One Line Title",
-    "cards": [
-      {
-        "type": "MOVIE",
-        "title": "Deep Into The Deep Sleep",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE",
-        "title": "We",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE",
-        "title": "The Fairy Story Of A Legend",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Cursed",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE",
-        "title": "My Crazy One",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Gone",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE",
-        "title": "A Cold Night To Stay",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE",
-        "title": "The Silence",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Hear The Roar",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Two Line Title + Icon",
-    "cards": [
-      {
-        "type": "MOVIE_BASE",
-        "title": "Deep Into The Deep Sleep",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "We",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "The Fairy Story Of A Legend",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Cursed",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "My Crazy One",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Gone",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "A Cold Night To Stay",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "The Silence",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Hear The Roar",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Two Line Title + Description + Icon",
-    "cards": [
-      {
-        "type": "MOVIE_COMPLETE",
-        "description": "$3.99",
-        "title": "Deep Into The Deep Sleep",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "We",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "The Fairy",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Cursed",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "My Crazy One",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Gone",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "A Cold Night To Stay",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "description": "$3.99",
-        "title": "The Silence",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Hear The Roar",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description",
-    "cards": [
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#bf360C",
-        "localImageResource": "card_image_music_02"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#b93221",
-        "localImageResource": "card_image_music_03"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#311b92",
-        "localImageResource": "card_image_music_04"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#33691e",
-        "localImageResource": "card_image_music_05"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#37474f",
-        "localImageResource": "card_image_music_06"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#3e2723",
-        "localImageResource": "card_image_music_08"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#01579B",
-        "localImageResource": "card_image_music_09"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description + Icon",
-    "cards": [
-      {
-        "type": "GAME",
-        "title": "Crazy One",
-        "description": "Purchased",
-        "localImageResource": "game_crazy_one",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Cursed",
-        "description": "Purchased",
-        "localImageResource": "game_cursed",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "fairy",
-        "description": "Purchased",
-        "localImageResource": "game_fairy",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Hear The Roar",
-        "description": "Purchased",
-        "localImageResource": "game_hear_the_roar",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Silence",
-        "description": "Purchased",
-        "localImageResource": "game_silence",
-        "footerIconLocalImageResource": "ic_installed"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description (Wide)",
-    "cards": [
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_01"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_02"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_03"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_04"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_05"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_06"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_07"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_08"
-      }
-    ]
-  },
-  {
-    "type" : 2
-  },
-  {
-    "type" : 1,
-    "title": "Advanced Examples"
-  },
-  {
-    "title": "BaseCardView Info On The Right",
-    "cards": [
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_02"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_03"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_04"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_05"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_06"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_07"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_08"
-      }
-    ]
-  },
-  {
-    "title": "BaseCardView using TextView",
-    "cards": [
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_01"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_02"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_03"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_04"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_05"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_06"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_07"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_08"
-      }
-    ]
-  },
-  {
-    "title": "ImageCardView Customize Style",
-    "cards": [
-      {
-        "type": "SINGLE_LINE",
-        "title": "Action & Adventure",
-        "footerColor": "#dd004e",
-        "localImageResource": "category_action"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Animation",
-        "footerColor": "#c51162",
-        "localImageResource": "category_animation"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Classics",
-        "footerColor": "#9c27b0",
-        "localImageResource": "category_classics"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Comedy",
-        "footerColor": "#cf4900",
-        "localImageResource": "category_comedy"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Crime",
-        "footerColor": "#3f51b5",
-        "localImageResource": "category_crime"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Documentary",
-        "footerColor": "#02639b",
-        "localImageResource": "category_documentary"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Drama",
-        "footerColor": "#2a56c6",
-        "localImageResource": "category_drama"
-      }
-    ]
-  },
-  {
-    "title": "ImageCardView with onFocusChange listener",
-    "shadow": false,
-    "cards": [
-      {
-        "type": "ICON",
-        "title": "Settings",
-        "localImageResource": "ic_settings_settings"
-      },
-      {
-        "type": "ICON",
-        "title": "WiFi",
-        "localImageResource": "ic_settings_wifi_3_bar"
-      },
-      {
-        "type": "ICON",
-        "title": "Parental Control",
-        "localImageResource": "ic_settings_parental_control"
-      },
-      {
-        "type": "ICON",
-        "title": "Time",
-        "localImageResource": "ic_settings_time"
-      }
-    ]
-  }
-]
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/detail_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/detail_example.json
deleted file mode 100644
index b6d06e2..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/detail_example.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
-  "title": "A Summer in a Canyon",
-  "description": "Kate Douglas Smith Wiggin",
-  "year": 1914,
-  "text": "It was nine o’clock one sunny California morning, and Geoffrey Strong stood under the live-oak trees in Las Flores Cañon, with a pot of black paint in one hand and a huge brush in the other.  He could have handled these implements to better purpose and with better grace had not his arms been firmly held by three laughing girls, who pulled not wisely, but too well.  He was further incommoded by the presence of a small urchin who lay on the dusty ground beneath his feet, fastening an upward clutch on the legs of his trousers.\n\nThere were three large canvas tents directly in front of them, yet no one of these seemed to be the object of dissension, but rather a redwood board, some three feet in length, which was nailed on a tree near by. twitch of her cousin’s sleeve.",
-  "localImageResource": "movie_poster_01",
-  "price": "$9.99",
-  "characters": [
-    {
-      "type": "CHARACTER",
-      "title": "Leonardo Di Caprio",
-      "localImageResource": "face_01"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Gerald Butler",
-      "localImageResource": "face_08"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Russle Crow",
-      "localImageResource": "face_02"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Emma Stone",
-      "localImageResource": "face_03"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Natalie Portman",
-      "localImageResource": "face_04"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Jack Gyllanhall",
-      "localImageResource": "face_05"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Ryan Gossling",
-      "localImageResource": "face_06"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Olivia Wilde",
-      "localImageResource": "face_07"
-    }
-  ],
-  "recommended": [
-    {
-      "type": "MOVIE",
-      "title": "The Amazing Spuder-Man",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_01"
-    },
-    {
-      "type": "MOVIE",
-      "title": "American Psycho",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_02"
-    },
-    {
-      "type": "MOVIE",
-      "title": "Big Hero 6",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_03"
-    },
-    {
-      "type": "MOVIE",
-      "title": "Edge of Tomorrow",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_04"
-    },
-    {
-      "type": "MOVIE",
-      "title": "The Hobbit: The Desolation of Smaug",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_05"
-    },
-    {
-      "type": "MOVIE",
-      "title": "Interstellar",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_06"
-    },
-    {
-      "type": "MOVIE",
-      "title": "Jurassic Park",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_07"
-    },
-    {
-      "type": "MOVIE",
-      "title": "The Hunger Games: Mockingjay Part I",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_08"
-    },
-    {
-      "type": "MOVIE",
-      "title": "Planes",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_09"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/grid_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/grid_example.json
deleted file mode 100644
index 56da5db..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/grid_example.json
+++ /dev/null
@@ -1,88 +0,0 @@
-{
-  "cards": [
-    {
-      "type": "GRID_SQUARE",
-      "title": "Nüsse",
-      "description": "$3.99/lb",
-      "localImageResource": "food_01"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Undefinierbar",
-      "description": "$3.99/lb",
-      "localImageResource": "food_02"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Brocoli",
-      "description": "$3.99/lb",
-      "localImageResource": "food_03"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Salat",
-      "description": "$3.99/lb",
-      "localImageResource": "food_04"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Große Radischen",
-      "description": "$3.99/lb",
-      "localImageResource": "food_05"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Rote Zwiebeln",
-      "description": "$3.99/lb",
-      "localImageResource": "food_06"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Lauch",
-      "description": "$3.99/lb",
-      "localImageResource": "food_07"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Exotisches Zeugs",
-      "description": "$3.99/lb",
-      "localImageResource": "food_08"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Zitronen",
-      "description": "$3.99/lb",
-      "localImageResource": "food_09"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Meerirgendwas",
-      "description": "$3.99/lb",
-      "localImageResource": "food_10"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Irgendein Kohl",
-      "description": "$3.99/lb",
-      "localImageResource": "food_11"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Apfel",
-      "description": "$3.99/lb",
-      "localImageResource": "food_12"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Mehr Äpfel",
-      "description": "$3.99/lb",
-      "localImageResource": "food_13"
-    },
-    {
-      "type": "GRID_SQUARE",
-      "title": "Tomaten",
-      "description": "$3.99/lb",
-      "localImageResource": "food_14"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/icon_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/icon_example.json
deleted file mode 100644
index 2fdfef5..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/icon_example.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
-  "title": "Icon",
-  "cards": [
-      {
-        "type": "ICON",
-        "title": "Settings",
-        "localImageResource": "ic_settings_settings"
-      },
-      {
-        "type": "ICON",
-        "title": "WiFi",
-        "localImageResource": "ic_settings_wifi_3_bar"
-      },
-      {
-        "type": "ICON",
-        "title": "Parental Control",
-        "localImageResource": "ic_settings_parental_control"
-      },
-      {
-        "type": "ICON",
-        "title": "Time",
-        "localImageResource": "ic_settings_time"
-      },
-      {
-        "type": "ICON",
-        "title": "Input connection",
-        "localImageResource": "icon_01"
-      },
-      {
-        "type": "ICON",
-        "title": "Hdmi",
-        "localImageResource": "icon_02"
-      },
-      {
-        "type": "ICON",
-        "title": "Overscan",
-        "localImageResource": "icon_03"
-      },
-      {
-        "type": "ICON",
-        "title": "Remote",
-        "localImageResource": "icon_04"
-      },
-      {
-        "type": "ICON",
-        "title": "Ethernet",
-        "localImageResource": "icon_05"
-      },
-      {
-        "type": "ICON",
-        "title": "Voice",
-        "localImageResource": "icon_06"
-      },
-      {
-        "type": "ICON",
-        "title": "Brightness",
-        "localImageResource": "icon_07"
-      },
-      {
-        "type": "ICON",
-        "title": "Antenna",
-        "localImageResource": "icon_08"
-      }
-    ]
-}
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/launcher_cards.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/launcher_cards.json
deleted file mode 100644
index 7508d97..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/launcher_cards.json
+++ /dev/null
@@ -1,70 +0,0 @@
-[
-  {
-    "title": "",
-    "cards": [
-      {
-        "id": 0,
-        "type": "DEFAULT",
-        "title": "Card Examples",
-        "localImageResource": "thumbnail_example_cards",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 1,
-        "type": "DEFAULT",
-        "title": "Page/Row fragment Examples",
-        "localImageResource": "thumbnail_example_browse",
-        "description": "Showcase usage of page/rows fragment"
-      },
-      {
-        "id": 2,
-        "type": "DEFAULT",
-        "title": "Grid Examples",
-        "localImageResource": "thumbnail_example_grid",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 3,
-        "type": "DEFAULT",
-        "title": "Detail Examples",
-        "localImageResource": "thumbnail_example_detail",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 4,
-        "type": "DEFAULT",
-        "title": "Video consumption Examples",
-        "localImageResource": "thumbnail_example_video_consumption",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 5,
-        "type": "DEFAULT",
-        "title": "Music consumption Examples",
-        "localImageResource": "thumbnail_example_music_consumption",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 6,
-        "type": "DEFAULT",
-        "title": "Wizard Examples",
-        "localImageResource": "thumbnail_example_wizard",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 7,
-        "type": "DEFAULT",
-        "title": "Settings Examples",
-        "localImageResource": "thumbnail_example_settings",
-        "description": "Showcase of various card design and layouts"
-      },
-      {
-        "id": 8,
-        "type": "DEFAULT",
-        "title": "Dialog Examples",
-        "localImageResource": "thumbnail_example_dialog",
-        "description": "Showcase of various card design and layouts"
-      }
-    ]
-  }
-]
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_consumption_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_consumption_example.json
deleted file mode 100644
index 23eb239..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_consumption_example.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
-  "songs": [
-    {
-      "number": 1,
-      "duration": "1:38",
-      "title": "Thump and Jump",
-      "description": "Jimmy Fontanez/Media Right Production",
-      "image": "card_image_music_01",
-      "file": "track_01"
-    },
-    {
-      "number": 2,
-      "duration": "1:42",
-      "title": "Give",
-      "description": "Silent Partner",
-      "image": "card_image_music_02",
-      "file": "track_02"
-    },
-    {
-      "number": 3,
-      "duration": "1:21",
-      "title": "Dusty Road",
-      "description": "Jingle Punks",
-      "image": "card_image_music_03",
-      "file": "track_03"
-    },
-    {
-      "number": 4,
-      "duration": "2:06",
-      "title": "Light The Torch",
-      "description": "Silent Partner",
-      "image": "card_image_music_04",
-      "file": "track_04"
-    },
-    {
-      "number": 5,
-      "duration": "2:43",
-      "title": "Blue Skies",
-      "description": "Silent Partner",
-      "image": "card_image_music_05",
-      "file": "track_05"
-    },
-    {
-      "number": 6,
-      "duration": "0:05",
-      "title": "In the Land of Rhinoplasty(Sting)",
-      "description": "Jingle Punks",
-      "image": "card_image_music_06",
-      "file": "track_06"
-    },
-    {
-      "number": 7,
-      "duration": "2:54",
-      "title": "Ice Crystals",
-      "description": "Everet Almond",
-      "image": "card_image_music_07",
-      "file": "track_07"
-    },
-    {
-      "number": 8,
-      "duration": "2:38",
-      "title": "Shoulder Closures",
-      "description": "Gunnar Olsen",
-      "image": "card_image_music_08",
-      "file": "track_08"
-    },
-    {
-      "number": 9,
-      "duration": "2:40",
-      "title": "The Coldest Shoulder",
-      "description": "The 126ers",
-      "image": "card_image_music_09",
-      "file": "track_09"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_example.json
deleted file mode 100644
index b01a2d4..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/music_example.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
-  "title": "A Summer in a Canyon",
-  "description": "Kate Douglas Smith Wiggin",
-  "year": 1914,
-  "text": "It was nine o’clock one sunny California morning, and Geoffrey Strong stood under the live-oak trees in Las Flores Cañon, with a pot of black paint in one hand and a huge brush in the other.  He could have handled these implements to better purpose and with better grace had not his arms been firmly held by three laughing girls, who pulled not wisely, but too well.  He was further incommoded by the presence of a small urchin who lay on the dusty ground beneath his feet, fastening an upward clutch on the legs of his trousers.\n\nThere were three large canvas tents directly in front of them, yet no one of these seemed to be the object of dissension, but rather a redwood board, some three feet in length, which was nailed on a tree near by. twitch of her cousin’s sleeve.",
-  "localImageResource": "movie_poster_01",
-  "price": "$9.99",
-  "characters": [
-    {
-      "type": "CHARACTER",
-      "title": "Leonardo Di Caprio",
-      "localImageResource": "face_01"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Gerald Butler",
-      "localImageResource": "face_08"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Russle Crow",
-      "localImageResource": "face_02"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Emma Stone",
-      "localImageResource": "face_03"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Natalie Portman",
-      "localImageResource": "face_04"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Jack Gyllanhall",
-      "localImageResource": "face_05"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Ryan Gossling",
-      "localImageResource": "face_06"
-    },
-    {
-      "type": "CHARACTER",
-      "title": "Olivia Wilde",
-      "localImageResource": "face_07"
-    }
-  ],
-  "recommended": [
-    {
-      "type": "THIN",
-      "title": "The Amazing Spuder-Man",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_01"
-    },
-    {
-      "type": "THIN",
-      "title": "American Psycho",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_02"
-    },
-    {
-      "type": "THIN",
-      "title": "Big Hero 6",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_03"
-    },
-    {
-      "type": "THIN",
-      "title": "Edge of Tomorrow",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_04"
-    },
-    {
-      "type": "THIN",
-      "title": "The Hobbit: The Desolation of Smaug",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_05"
-    },
-    {
-      "type": "THIN",
-      "title": "Interstellar",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_06"
-    },
-    {
-      "type": "THIN",
-      "title": "Jurassic Park",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_07"
-    },
-    {
-      "type": "THIN",
-      "title": "The Hunger Games: Mockingjay Part I",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_08"
-    },
-    {
-      "type": "THIN",
-      "title": "Planes",
-      "description": "$3.99",
-      "localImageResource": "card_image_movie_09"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json
deleted file mode 100644
index 5e686d7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/page_row_example.json
+++ /dev/null
@@ -1,515 +0,0 @@
-[
-  {
-    "title": "One Line Title",
-    "cards": [
-      {
-        "type": "MOVIE",
-        "title": "Deep Into The Deep Sleep",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE",
-        "title": "We",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE",
-        "title": "The Fairy Story Of A Legend",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Cursed",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE",
-        "title": "My Crazy One",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Gone",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE",
-        "title": "A Cold Night To Stay",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE",
-        "title": "The Silence",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE",
-        "title": "Hear The Roar",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Two Line Title",
-    "cards": [
-      {
-        "type": "MOVIE_BASE",
-        "title": "Deep Into The Deep Sleep",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "We",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "The Fairy Story Of A Legend",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Cursed",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "My Crazy One",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Gone",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "A Cold Night To Stay",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "The Silence",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE_BASE",
-        "title": "Hear The Roar",
-        "description": "$3.99",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Two Line Title + Icon",
-    "cards": [
-      {
-        "type": "MOVIE_COMPLETE",
-        "description": "$3.99",
-        "title": "Deep Into The Deep Sleep",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_01"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "We",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_02"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "The Fairy",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_03"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Cursed",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_04"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "My Crazy One",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_05"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Gone",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_06"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "A Cold Night To Stay",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_07"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "description": "$3.99",
-        "title": "The Silence",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_08"
-      },
-      {
-        "type": "MOVIE_COMPLETE",
-        "title": "Hear The Roar",
-        "description": "$3.99",
-        "footerIconLocalImageResource": "stars_red",
-        "localImageResource": "card_image_movie_09"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description",
-    "cards": [
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#bf360C",
-        "localImageResource": "card_image_music_02"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#b93221",
-        "localImageResource": "card_image_music_03"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#311b92",
-        "localImageResource": "card_image_music_04"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#33691e",
-        "localImageResource": "card_image_music_05"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#37474f",
-        "localImageResource": "card_image_music_06"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#3e2723",
-        "localImageResource": "card_image_music_08"
-      },
-      {
-        "type": "SQUARE_BIG",
-        "title": "Blue in Green",
-        "description": "Miles Davis",
-        "footerColor": "#01579B",
-        "localImageResource": "card_image_music_09"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description + Icon",
-    "cards": [
-      {
-        "type": "GAME",
-        "title": "Crazy One",
-        "description": "Purchased",
-        "localImageResource": "game_crazy_one",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Cursed",
-        "description": "Purchased",
-        "localImageResource": "game_cursed",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "fairy",
-        "description": "Purchased",
-        "localImageResource": "game_fairy",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Hear The Roar",
-        "description": "Purchased",
-        "localImageResource": "game_hear_the_roar",
-        "footerIconLocalImageResource": "ic_installed"
-      },
-      {
-        "type": "GAME",
-        "title": "Silence",
-        "description": "Purchased",
-        "localImageResource": "game_silence",
-        "footerIconLocalImageResource": "ic_installed"
-      }
-    ]
-  },
-  {
-    "title": "Title + Description (Wide)",
-    "cards": [
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_01"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_02"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_03"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_04"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_05"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_06"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_07"
-      },
-      {
-        "type": "DEFAULT",
-        "title": "Marseille sea food tour",
-        "description": "9,089 views   3 years ago   by ADELAIDE",
-        "localImageResource": "coffee_and_tea_08"
-      }
-    ]
-  },
-  {
-    "title": "BaseCardView Info On The Right",
-    "cards": [
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_02"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_03"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_04"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_05"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_06"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_07"
-      },
-      {
-        "type": "SIDE_INFO",
-        "title": "The Life Aquatic",
-        "description": "Seu Jorge",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "card_image_music_08"
-      }
-    ]
-  },
-  {
-    "title": "BaseCardView using TextView",
-    "cards": [
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_01"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_02"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_03"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_04"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_05"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_06"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_07"
-      },
-      {
-        "type": "TEXT",
-        "title": "Jonathan Max",
-        "extraText": "Bacon ipsum dolor amet bresaola kevin tenderloin swine shoulder strip steak t-bone picanha turducken beef. Ribeye turkey t-bone pastrami meatball corned beef. Pork belly landjaeger short ribs ground round cupim, brisket ham tri-tip. Pig pork loin hamburger picanha ribeye, pork belly meatball chicken ham boudin sirloin corned beef frankfurter ham hock.",
-        "localImageResource": "face_08"
-      }
-    ]
-  },
-  {
-    "title": "ImageCardView Customize Style",
-    "cards": [
-      {
-        "type": "SINGLE_LINE",
-        "title": "Action & Adventure",
-        "footerColor": "#dd004e",
-        "localImageResource": "category_action"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Animation",
-        "footerColor": "#c51162",
-        "localImageResource": "category_animation"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Classics",
-        "footerColor": "#9c27b0",
-        "localImageResource": "category_classics"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Comedy",
-        "footerColor": "#cf4900",
-        "localImageResource": "category_comedy"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Crime",
-        "footerColor": "#3f51b5",
-        "localImageResource": "category_crime"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Documentary",
-        "footerColor": "#02639b",
-        "localImageResource": "category_documentary"
-      },
-      {
-        "type": "SINGLE_LINE",
-        "title": "Drama",
-        "footerColor": "#2a56c6",
-        "localImageResource": "category_drama"
-      }
-    ]
-  },
-  {
-    "title": "ImageCardView with onFocusChange listener",
-    "shadow": false,
-    "cards": [
-      {
-        "type": "ICON",
-        "title": "Settings",
-        "localImageResource": "ic_settings_settings"
-      },
-      {
-        "type": "ICON",
-        "title": "WiFi",
-        "localImageResource": "ic_settings_wifi_3_bar"
-      },
-      {
-        "type": "ICON",
-        "title": "Parental Control",
-        "localImageResource": "ic_settings_parental_control"
-      },
-      {
-        "type": "ICON",
-        "title": "Time",
-        "localImageResource": "ic_settings_time"
-      }
-    ]
-  }
-]
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_01.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_01.mp3
deleted file mode 100755
index 9762383..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_01.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_02.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_02.mp3
deleted file mode 100755
index 82f4c8a..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_02.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_03.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_03.mp3
deleted file mode 100755
index 7faaeea..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_03.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_04.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_04.mp3
deleted file mode 100755
index 90a5310..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_04.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_05.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_05.mp3
deleted file mode 100755
index ec65400..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_05.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_06.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_06.mp3
deleted file mode 100755
index fe84974..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_06.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_07.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_07.mp3
deleted file mode 100755
index 9f876c0..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_07.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_08.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_08.mp3
deleted file mode 100755
index 33c8af8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_08.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_09.mp3 b/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_09.mp3
deleted file mode 100755
index aaed617..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/track_09.mp3
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/raw/wizard_example.json b/samples/SupportLeanbackShowcase/app/src/main/res/raw/wizard_example.json
deleted file mode 100644
index f46b856..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/raw/wizard_example.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "title": "Androidify! The Movie",
-  "breadcrump": "Android TV",
-  "price_hd": "$4.99",
-  "price_sd": "$2.99"
-}
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/arrays.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/arrays.xml
deleted file mode 100644
index 9933ba7..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/arrays.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-
-<resources>
-    <array name="pref_parent_control_entries">
-        <item>Everyone</item>
-        <item>Low maturity</item>
-        <item>Medium maturity</item>
-        <item>High maturity</item>
-    </array>
-    <array name="pref_parent_control_entries_values">
-        <item>everyone</item>
-        <item>low</item>
-        <item>medium</item>
-        <item>high</item>
-    </array>
-    <array name="pref_parent_control_entries_summaries">
-        <item>This description becomes visible only on focus.</item>
-        <item>This description becomes visible only on focus.</item>
-        <item>This description becomes visible only on focus.</item>
-        <item>This description becomes visible only on focus.</item>
-    </array>
-    <array name="pref_wifi_networks">
-        <item>Wi-Fi network 1</item>
-        <item>Wi-Fi network 2</item>
-        <item>Wi-Fi network 3</item>
-        <item>Wi-Fi network 4</item>
-        <item>Wi-Fi network 5</item>
-        <item>Wi-Fi network 6</item>
-    </array>
-</resources>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml
deleted file mode 100644
index 8526c76..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-<resources>
-    <color name="background_gradient_start">#FFFFFF</color>
-    <color name="background_gradient_end">#DDDDDD</color>
-    <color name="fastlane_background">#06838f</color>
-    <color name="fastlane_teal_background">#00796B</color>
-    <color name="search_opaque">#ffaa3f</color>
-    <color name="search_color">#FFEB3B</color>
-    <color name="search_bright_color">#FFEB3B</color>
-    <color name="search_icon_color">#222222</color>
-    <color name="accent">#80D3FB</color>
-    <color name="settings_background">#00695C</color>
-
-    <color name="default_background">#3d3d3d</color>
-
-    <color name="default_card_background_color">#263238</color>
-    <color name="default_card_footer_background_color">#37474F</color>
-    <color name="selected_card_footer_background_color">#F0F</color>
-
-    <color name="lb_default_brand_color">#FF455A64</color>
-    <color name="card_primary_text">#EEEEEE</color>
-    <color name="card_secondary_text">#99EEEEEE</color>
-
-    <color name="loading_error_card_background">#86c739</color>
-
-    <color name="card_examples_background">#222222</color>
-
-    <color name="detail_view_actionbar_background">#04549D</color>
-    <color name="detail_view_background">#0374BF</color>
-    <color name="detail_view_related_background">#022A4E</color>
-    <color name="song_row_favorite_color">#FF6E40</color>
-
-    <color name="app_guidedstep_actions_background">#C03800</color>
-    <color name="app_guidedstep_subactions_background">#732200</color>
-    <color name="app_guidedstep_dialog_actions_background">#263238</color>
-    <color name="settings_card_background">#0277BD</color>
-    <color name="settings_card_background_focussed">#01579B</color>
-
-    <color name="player_progress_color">#feab91</color>
-    <color name="player_background_color">#db2a0f</color>
-
-</resources>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/dims.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/dims.xml
deleted file mode 100644
index 07c8027..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/dims.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-<resources>
-    <dimen name="default_image_card_width">224dp</dimen>
-    <dimen name="default_image_card_height">126dp</dimen>
-
-    <dimen name="movie_image_card_width">120dp</dimen>
-    <dimen name="movie_image_card_height">172dp</dimen>
-
-    <dimen name="icon_image_card_width">224dp</dimen>
-    <dimen name="icon_image_card_height">109dp</dimen>
-
-    <dimen name="big_square_image_card_width">144dp</dimen>
-    <dimen name="big_square_image_card_height">144dp</dimen>
-
-    <dimen name="square_image_card_width">128dp</dimen>
-    <dimen name="square_image_card_height">128dp</dimen>
-
-    <dimen name="small_square_image_card_width">96dp</dimen>
-    <dimen name="small_square_image_card_height">96dp</dimen>
-
-    <dimen name="sidetext_image_card_width">144dp</dimen>
-    <dimen name="sidetext_image_card_height">144dp</dimen>
-
-    <dimen name="wide_short_image_card_width">224dp</dimen>
-    <dimen name="wide_short_image_card_height">109dp</dimen>
-
-    <dimen name="character_image_card_width">120dp</dimen>
-    <dimen name="character_image_card_height">120dp</dimen>
-
-    <dimen name="grid_card_width">200dp</dimen>
-    <dimen name="grid_card_height">200dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml
deleted file mode 100644
index 0921d66..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-<resources>
-    <string name="app_name">ShowcaseApp</string>
-    <string name="browse_title"><![CDATA[androidTV]]></string>
-
-    <!-- Error messages -->
-    <string name="implement_search">Implement your own in-app search.</string>
-    <string name="card_examples_title">Card Examples</string>
-    <string name="detail_view_title">DetailView Example</string>
-    <string name="action_cicked">Action clicked. Implement your own handler.</string>
-    <string name="grid_example_title">Grid Example</string>
-    <string name="action_buy">Buy </string>
-    <string name="action_wishlist">Add to wishlist</string>
-    <string name="action_related">Related</string>
-    <string name="header_related">Related Items</string>
-    <string name="header_recommended">Recommended</string>
-
-    <!-- Page/List Row -->
-    <string name="page_list_row_title">Page and List Row</string>
-
-    <!-- Dialog example -->
-    <string name="dialog_example_title">Use Google\'s location service?</string>
-    <string name="dialog_example_description">Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.</string>
-    <string name="dialog_example_button_positive">Agree</string>
-    <string name="dialog_example_button_negative">Disagree</string>
-    <string name="dialog_example_button_toast_positive_clicked">\'Agree\' clicked.</string>
-    <string name="dialog_example_button_toast_negative_clicked">\'Disagree\' clicked.</string>
-
-    <!-- Wizard example -->
-    <string name="wizard_example_choose_rent_options">Choose rent options</string>
-    <string name="wizard_example_watch_hd">Watch in HD on supported devices</string>
-    <string name="wizard_example_watch_sd">Watch in standard definition on the web and supported devices</string>
-    <string name="wizard_example_rental_period">Rental period: start within 30 days,\nfinish within 24 hours</string>
-    <string name="wizard_example_input_card">Enter credit card number</string>
-    <string name="wizard_example_expiration_date">Exp Date</string>
-    <string name="wizard_example_payment_method">Payment Method</string>
-    <string name="wizard_example_toast_payment_method_clicked">\'Payment Method\' clicked.</string>
-    <string name="wizard_example_rent">Rent</string>
-    <string name="wizard_example_rent_hd">Rent HD</string>
-    <string name="wizard_example_rent_sd">Rent SD</string>
-    <string name="wizard_example_processing">Processinig...</string>
-    <string name="wizard_example_watch_now">Watch now</string>
-    <string name="wizard_example_later">Later</string>
-    <string name="wizard_example_watch_now_clicked">\'Watch now\' clicked.</string>
-    <string name="wizard_example_later_clicked">\'Later\' clicked.</string>
-    <string name="wizard_example_new_payment_guidance_title">New credit card</string>
-    <string name="wizard_example_new_payment_guidance_description">Enter new credit card information here</string>
-    <string name="wizard_example_input_credit">Input Payment Type</string>
-    <string name="wizard_example_visa">Visa-%s</string>
-    <string name="wizard_example_master">Master-%s</string>
-    <string name="wizard_example_input_credit_wrong">Error: invalid credit card number</string>
-    <string name="wizard_example_just_a_second">Just a second...</string>
-</resources>
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml
deleted file mode 100644
index 4f624d8..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,271 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-<resources>
-
-    <style name="AppTheme" parent="@style/Theme.Leanback">
-    </style>
-
-    <!-- Various movie card styles. Used in cards example. -->
-    <style name="MovieCardBadgeStyle" parent="Widget.Leanback.ImageCardView.BadgeStyle">
-        <item name="android:src">@drawable/stars_red</item>
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:scaleType">center</item>
-    </style>
-
-    <style name="MovieCardTitleTwoLineStyle" parent="Widget.Leanback.ImageCardView.TitleStyle">
-        <item name="android:maxLines">2</item>
-        <item name="android:minLines">2</item>
-    </style>
-
-    <style name="MovieCardContentGoneStyle" parent="Widget.Leanback.ImageCardView.ContentStyle">
-        <item name="android:visibility">invisible</item>
-    </style>
-
-    <style name="MovieCardContentStyle" parent="Widget.Leanback.ImageCardView.ContentStyle">
-        <item name="android:gravity">right</item>
-    </style>
-
-    <style name="MovieCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">@dimen/movie_image_card_width</item>
-        <item name="android:layout_height">@dimen/movie_image_card_height</item>
-    </style>
-
-
-    <style name="MovieCardTitleOnlyStyle" parent="Widget.Leanback.ImageCardViewStyle">
-        <item name="lbImageCardViewType">Title</item>
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- Theme corresponding to the MovieCardSimpleStyle -->
-    <style name="MovieCardSimpleTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/MovieCardTitleOnlyStyle </item>
-        <item name="imageCardViewImageStyle">@style/MovieCardImageStyle</item>
-    </style>
-
-    <style name="MovieCardCompleteStyle" parent="MovieCardTitleOnlyStyle">
-        <item name="lbImageCardViewType">Title|Content|IconOnLeft</item>
-    </style>
-
-    <!-- Theme corresponding to the MovieCardCompleteStyle -->
-    <style name="MovieCardCompleteTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/MovieCardCompleteStyle </item>
-        <item name="imageCardViewImageStyle">@style/MovieCardImageStyle</item>
-        <item name="imageCardViewTitleStyle">@style/MovieCardTitleTwoLineStyle</item>
-        <item name="imageCardViewBadgeStyle">@style/MovieCardBadgeStyle</item>
-        <item name="imageCardViewContentStyle">@style/MovieCardContentStyle</item>
-    </style>
-
-    <!-- Theme corresponding to the MovieCardBasicStyle -->
-    <style name="MovieCardBasicTheme" parent="MovieCardCompleteTheme">
-        <item name="imageCardViewContentStyle">@style/MovieCardContentGoneStyle</item>
-    </style>
-
-    <!-- Squared Title/Content card style. Used in cards example. -->
-    <style name="SquareBigCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">@dimen/big_square_image_card_width</item>
-        <item name="android:layout_height">@dimen/big_square_image_card_height</item>
-    </style>
-
-    <style name="SquareBigCard" parent="Widget.Leanback.ImageCardViewStyle">
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- Theme corresponding to the SquareBigCard -->
-    <style name="SquareBigCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/SquareBigCard </item>
-        <item name="imageCardViewImageStyle">@style/SquareBigCardImageStyle</item>
-    </style>
-
-    <!-- SideInfo. used in cards example -->
-    <style name="SideInfoCardStyle" parent="Widget.Leanback.BaseCardViewStyle">
-        <item name="cardType">mainOnly</item>
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- TextCardView. used in cards example -->
-    <style name="TextCardStyle" parent="Widget.Leanback.BaseCardViewStyle">
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- CharacterCardView. used in details example -->
-    <style name="CharacterCardStyle" parent="Widget.Leanback.BaseCardViewStyle">
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- Grid card style. Used by Grid example. -->
-    <style name="GridCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">@dimen/grid_card_width</item>
-        <item name="android:layout_height">@dimen/grid_card_height</item>
-    </style>
-
-    <style name="GridCardStyle" parent="Widget.Leanback.ImageCardViewStyle">
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <!-- Theme corresponding to the GridCardTheme -->
-    <style name="GridCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/GridCardStyle </item>
-        <item name="imageCardViewImageStyle">@style/GridCardImageStyle</item>
-    </style>
-
-    <!-- A default card style. Used in cards example. -->
-    <style name="DefaultCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">@dimen/default_image_card_width</item>
-        <item name="android:layout_height">@dimen/default_image_card_height</item>
-    </style>
-
-    <style name="DefaultCardStyle" parent="Widget.Leanback.ImageCardViewStyle">
-        <item name="cardBackground">@null</item>
-    </style>
-
-    <style name="DefaultCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/DefaultCardStyle </item>
-        <item name="imageCardViewImageStyle">@style/DefaultCardImageStyle</item>
-    </style>
-
-    <!-- Game card styles with custom Badge icon. Used in cards example. -->
-    <style name="GameCardContentStyle" parent="Widget.Leanback.ImageCardView.ContentStyle">
-        <item name="android:textColor">#80c349</item>
-    </style>
-
-    <style name="GameCardBadgeStyle" parent="Widget.Leanback.ImageCardView.BadgeStyle">
-        <item name="android:src">@drawable/ic_installed</item>
-    </style>
-
-    <style name="GameCardStyle" parent="DefaultCardStyle">
-        <item name="lbImageCardViewType">Title|Content|IconOnRight</item>
-    </style>
-
-    <!-- Theme corresponding to the GameCardStyle -->
-    <style name="GameCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/GameCardStyle </item>
-        <item name="imageCardViewContentStyle">@style/GameCardContentStyle</item>
-        <item name="imageCardViewBadgeStyle">@style/GameCardBadgeStyle</item>
-        <item name="imageCardViewImageStyle">@style/DefaultCardImageStyle</item>
-    </style>
-
-    <!-- Squared single line card with colored footer style. Used in cards example. -->
-    <style name="SingleLineCardTitleStyle" parent="Widget.Leanback.ImageCardView.TitleStyle">
-        <item name="android:textAlignment">center</item>
-        <item name="android:gravity">center</item>
-    </style>
-
-    <style name="SingleLineCardInfoAreaStyle" parent="Widget.Leanback.ImageCardView.InfoAreaStyle">
-        <item name="android:layout_width">@dimen/square_image_card_width</item>
-        <item name="layout_viewType">main</item>
-    </style>
-
-    <style name="SingleLineCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">@dimen/square_image_card_width</item>
-        <item name="android:layout_height">@dimen/square_image_card_height</item>
-    </style>
-
-    <style name="SingleLineCardStyle" parent="DefaultCardStyle">
-        <item name="lbImageCardViewType">Title</item>
-    </style>
-
-    <!-- Theme corresponding to the SingleLineCardStyle -->
-    <style name="SingleLineCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/SingleLineCardStyle </item>
-        <item name="imageCardViewTitleStyle">@style/SingleLineCardTitleStyle</item>
-        <item name="imageCardViewImageStyle">@style/SingleLineCardImageStyle</item>
-        <item name="imageCardViewInfoAreaStyle">@style/SingleLineCardInfoAreaStyle</item>
-    </style>
-
-
-    <!-- Icon card style with custom focus handler. Used in cards example. -->
-    <style name="IconCardViewStyle" parent="DefaultCardStyle">
-        <item name="lbImageCardViewType">Title</item>
-    </style>
-
-    <style name="IconCardImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
-        <item name="android:layout_width">96dp</item>
-        <item name="android:layout_height">96dp</item>
-        <item name="android:padding">16dp</item>
-    </style>
-
-    <style name="IconCardTitleStyle" parent="Widget.Leanback.ImageCardView.TitleStyle">
-        <item name="android:maxLines">2</item>
-        <item name="android:minLines">2</item>
-        <item name="android:gravity">center</item>
-    </style>
-
-    <style name="IconCardInfoAreaStyle" parent="Widget.Leanback.ImageCardView.InfoAreaStyle">
-        <item name="android:layout_width">96dp</item>
-        <item name="android:background">@null</item>
-        <item name="layout_viewType">main</item>
-    </style>
-
-    <!-- Theme corresponding to the IconCardStyle -->
-    <style name="IconCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle"> @style/IconCardViewStyle </item>
-        <item name="imageCardViewTitleStyle">@style/IconCardTitleStyle</item>
-        <item name="imageCardViewImageStyle">@style/IconCardImageStyle</item>
-        <item name="imageCardViewInfoAreaStyle">@style/IconCardInfoAreaStyle</item>
-    </style>
-
-
-    <style name="MediaListHeaderStyle" parent="Widget.Leanback.PlaybackMediaListHeaderStyle">
-        <item name="android:background">#282248</item>
-    </style>
-
-    <style name="SharedMediaItemRowStyle" parent="Widget.Leanback.PlaybackMediaItemRowStyle">
-        <item name="android:background">#282248</item>
-    </style>
-
-    <style name="RegularMediaItemTextStyle" parent="TextAppearance.Leanback.PlaybackMediaItemNumber">
-        <item name="android:textColor">#FF6255</item>
-        <item name="android:textSize">18sp</item>
-        <item name="android:fontFamily">sans-serif-light</item>
-    </style>
-
-
-    <style name="RegularMediaItemNumberStyle" parent="Widget.Leanback.PlaybackMediaItemNumberStyle">
-        <item name="android:visibility">visible</item>
-        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
-    </style>
-
-    <style name="RegularMediaItemNameStyle" parent="Widget.Leanback.PlaybackMediaItemNameStyle">
-        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
-    </style>
-
-    <style name="RegularMediaItemDurationStyle" parent="Widget.Leanback.PlaybackMediaItemDurationStyle">
-        <item name="android:visibility">visible</item>
-        <!--<item name="android:textAppearance">@style/OddMediaItemNumberTextStyle</item>-->
-    </style>
-
-
-    <style name="FavoriteMediaItemTextStyle" parent="TextAppearance.Leanback.PlaybackMediaItemNumber">
-        <item name="android:textColor">#FF6E40</item>
-        <item name="android:textSize">18sp</item>
-        <item name="android:fontFamily">sans-serif-medium</item>
-    </style>
-
-    <style name="FavoriteMediaItemNumberStyle" parent="Widget.Leanback.PlaybackMediaItemNumberStyle">
-        <item name="android:visibility">visible</item>
-        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
-    </style>
-
-    <style name="FavoriteMediaItemNameStyle" parent="Widget.Leanback.PlaybackMediaItemNameStyle">
-        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
-    </style>
-
-    <style name="FavoriteMediaItemDurationStyle" parent="Widget.Leanback.PlaybackMediaItemDurationStyle">
-        <item name="android:visibility">visible</item>
-        <item name="android:textAppearance">@style/FavoriteMediaItemTextStyle</item>
-    </style>
-
-</resources>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml b/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
deleted file mode 100644
index 4aa06f6..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
-  ~ in compliance with the License. You may obtain a copy of the License at
-  ~
-  ~ http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software distributed under the License
-  ~ is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
-  ~ or implied. See the License for the specific language governing permissions and limitations under
-  ~ the License.
-  ~
-  -->
-<resources>
-
-    <style name="Theme.Example.Leanback" parent="Theme.Leanback">
-        <item name="android:colorPrimary">#00A2B8</item>
-        <item name="android:colorAccent">@color/accent</item>
-        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackLauncher" parent="Theme.Example.Leanback">
-        <item name="android:windowBackground">@drawable/bg_living_room_wide</item>
-        <item name="browseRowsMarginTop">275dp</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackBrowse" parent="Theme.Leanback.Browse">
-        <item name="android:windowBackground">@color/card_examples_background</item>
-        <item name="defaultBrandColor">@color/fastlane_background</item>
-        <item name="defaultSearchColor">@color/search_color</item>
-        <item name="defaultSearchIconColor">@color/search_icon_color</item>
-        <item name="defaultSearchBrightColor">@color/search_bright_color</item>
-    </style>
-
-    <style name="Theme.Example.Leanback.CustomTitle" parent="Theme.Example.LeanbackBrowse">
-        <item name="browseTitleViewLayout">@layout/titleview</item>
-        <item name="browseRowsMarginTop">120dp</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackVerticalGrid" parent="Theme.Leanback.VerticalGrid">
-        <item name="android:windowBackground">@drawable/background_food</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackWizard" parent="Theme.Leanback.GuidedStep">
-        <item name="guidedActionsBackground">@color/app_guidedstep_actions_background</item>
-        <item name="guidedActionsBackgroundDark">@color/app_guidedstep_subactions_background</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackWizard.NoSelector">
-        <item name="guidedActionsSelectorDrawable">@null</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackDialog" parent="Theme.Leanback.GuidedStep">
-        <item name="guidedActionsBackground">@color/app_guidedstep_dialog_actions_background</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackPreferences" parent="Theme.Leanback">
-        <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:backgroundDimEnabled">true</item>
-        <item name="android:colorPrimary">@color/settings_background</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackDetails" parent="Theme.Leanback.Details">
-        <item name="android:colorPrimary">@color/detail_view_actionbar_background</item>
-        <item name="android:windowBackground">@drawable/background_canyon</item>
-     </style>
-
-    <style name="Theme.Example.LeanbackMusic" parent="Theme.Example.Leanback">
-        <item name="android:windowBackground">@drawable/background_sax</item>
-     </style>
-
-    <style name="Theme.Example.LeanbackMusic.RegularSongNumbers">
-        <!--<item name="playbackMediaItemRowStyle">@style/SharedMediaItemRowStyle</item>-->
-        <item name="playbackMediaItemNumberStyle">@style/RegularMediaItemNumberStyle</item>
-        <item name="playbackMediaItemNameStyle">@style/RegularMediaItemNameStyle</item>
-        <item name="playbackMediaItemDurationStyle">@style/RegularMediaItemDurationStyle</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackMusic.FavoriteSongNumbers">
-        <!--<item name="playbackMediaItemRowStyle">@style/SharedMediaItemRowStyle</item>-->
-        <item name="playbackMediaItemNumberStyle">@style/FavoriteMediaItemNumberStyle</item>
-        <item name="playbackMediaItemNameStyle">@style/FavoriteMediaItemNameStyle</item>
-        <item name="playbackMediaItemDurationStyle">@style/FavoriteMediaItemDurationStyle</item>
-    </style>
-
-    <style name="Theme.Example.LeanbackMusic.TrackListHeader">
-        <item name="playbackMediaListHeaderStyle">@style/MediaListHeaderStyle</item>
-    </style>
-
-</resources>
diff --git a/samples/SupportLeanbackShowcase/app/src/main/res/xml/prefs.xml b/samples/SupportLeanbackShowcase/app/src/main/res/xml/prefs.xml
deleted file mode 100644
index f0edd0e..0000000
--- a/samples/SupportLeanbackShowcase/app/src/main/res/xml/prefs.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto"
-                  android:summary="This is a dummy activitiy only to show case how to build a settings in an application. Changing configurations in this example doesn't affect anything."
-                  android:title="Settings Example">
-    <PreferenceScreen
-        android:icon="@drawable/ic_settings_wifi_4_bar"
-        android:key="prefs_wifi_screen_key"
-        android:title="Wi-Fi">
-        <PreferenceCategory
-            android:key="prefs_wifi_networks_key"
-            android:title="Available Wi-Fi networks">
-            <ListPreference
-                android:defaultValue="-1"
-                android:entries="@array/pref_wifi_networks"
-                android:entryValues="@array/pref_wifi_networks"
-                android:title="Available Wi-Fi networks"
-                android:key="prefs_wifi_key">
-            </ListPreference>
-        </PreferenceCategory>
-        <PreferenceCategory
-            android:key="prefs_wifi_others_key"
-            android:title="Other options">
-            <Preference
-                android:title="Connect via WPS"
-                android:key="prefs_wifi_connect_wps"><!-- You can use Intents here -->
-            </Preference>
-        </PreferenceCategory>
-    </PreferenceScreen>
-    <PreferenceScreen
-        android:icon="@drawable/ic_settings_time"
-        android:key="prefs_date_time_screen_key"
-        android:title="Date &amp; time">
-        <CheckBoxPreference
-            android:defaultValue="true"
-            android:disableDependentsState="true"
-            android:key="prefs_date_time_automatic"
-            android:summaryOff="On"
-            android:summaryOn="Off"
-            android:title="Automatic date  &amp; time"></CheckBoxPreference>
-        <Preference
-            android:dependency="prefs_date_time_automatic"
-            android:key="prefs_date"
-            android:summary="01/01/1970"
-            android:title="Date"></Preference>
-        <Preference
-            android:dependency="prefs_date_time_automatic"
-            android:key="prefs_time"
-            android:summary="00:43 PM"
-            android:title="Time"></Preference>
-        <CheckBoxPreference
-            android:defaultValue="true"
-            android:disableDependentsState="true"
-            android:key="prefs_date_time_use_timezone"
-            android:summary="Use network provided time zone"
-            android:title="Automatic time zone"></CheckBoxPreference>
-        <Preference
-            android:dependency="prefs_date_time_use_timezone"
-            android:summary="GMT 07:00 Pacific Daylight Time"
-            android:title="Time zone"></Preference>
-    </PreferenceScreen>
-    <ListPreference
-        android:defaultValue="everyone"
-        android:icon="@drawable/ic_settings_parental_control"
-        android:entries="@array/pref_parent_control_entries"
-        android:entryValues="@array/pref_parent_control_entries_values"
-        android:key="prefs_parental_control_level_key"
-        android:dialogMessage="Allow contents rated for"
-        android:title="Parental Control">
-    </ListPreference>
-    <PreferenceScreen
-        android:icon="@drawable/ic_settings_apps"
-        android:key="prefs_apps_screen_key"
-        android:title="Apps">
-        <PreferenceCategory
-            android:key="prefs_app_settings_category"
-            android:title="Apps settings">
-            <PreferenceScreen
-                android:key="prefs_app_settings_screen_key"
-                android:summary="Lorem ipsum dolor sit amet consectur adipiscing."
-                android:title="App permissions">
-                <Preference
-                    android:icon="@mipmap/app_banner_sample_app"
-                    android:summary="45.5 MB"
-                    android:selectable="false"
-                    android:title="Application A"></Preference>
-                <Preference
-                    android:selectable="false"
-                    android:summary="Hier steht ein voelligst sinnfreier Text den ja sowieso niemandhier lesen kann. Deshalb macht es auch keinen Unterschied ob hier sinnvolles und nicht so sinnvolles Zeug steht. Hm... Sasha, du kannst das vielleicht lesen und denkst dir jetzt auch, dass ich voll haengen geblieben bin, oder?... ^_^"></Preference>
-                <SwitchPreference
-                    android:title="Notifications"
-                    android:key="pref_force_stop"><!-- Start an Intent --></SwitchPreference>
-                <Preference
-                    android:title="Uninstall"
-                    android:key="pref_uninstall"><!-- Start an Intent --></Preference>
-                <Preference
-                    android:title="More Information"
-                    android:key="pref_more_info"></Preference>
-            </PreferenceScreen>
-        </PreferenceCategory>
-        <PreferenceCategory
-            android:key="prefs_app_downloaded_apps_category"
-            android:title="Downloaded Apps">
-            <ListPreference
-                android:defaultValue="everyone"
-                android:entries="@array/pref_parent_control_entries"
-                android:entryValues="@array/pref_parent_control_entries_values"
-                android:key="prefs_parental_control_level_key"
-                android:title="Downloaded Apps">
-            </ListPreference>
-        </PreferenceCategory>
-    </PreferenceScreen>
-</PreferenceScreen>
diff --git a/samples/SupportLeanbackShowcase/build-local.py b/samples/SupportLeanbackShowcase/build-local.py
deleted file mode 100644
index 2677f3a..0000000
--- a/samples/SupportLeanbackShowcase/build-local.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# BUILDING SupportLeanbackShowcase app using local library.
-import sys
-import subprocess
-import os
-import fileinput
-import re
-
-# Does an inplace substitution of the pattern with newVal in inputFile
-def replace(inputFile, pattern, newVal, ):
-  print 'About to replace repo path to {0} in {1}'.format(newVal, inputFile)
-  replaced = False
-  if os.path.exists(inputFile):
-    for line in fileinput.input(inputFile, inplace = 1):
-      if re.match(pattern, line, re.I|re.M):
-        line = re.sub(pattern, newVal, line)
-        replaced = True
-      print line,
-
-  if not replaced:
-    file = open(inputFile, "a")
-    file.write(newVal + "\n")
-
-# Finds the local leanback library version based on leanback-v17/maven-metadata.xml
-def lookup_local_library_version(repo_path):
-  leanback_maven_metadata_path = repo_path + "/out/host/gradle/frameworks/support/build/support_repo/com/android/support/leanback-v17/maven-metadata.xml"
-  if not os.path.exists(leanback_maven_metadata_path):
-    print "ERROR: Missing leanback-v17 library {} in local repo".format(leanback_maven_metadata_path)
-
-  file = open(leanback_maven_metadata_path, "r")
-  for line in file:
-    matchObj = re.match(r'\s*<version>(.*)</version>', line)
-    if matchObj:
-      return matchObj.group(1).strip(' \t\n\r')
-
-# Get repo path
-current_path = os.getcwd()
-index = current_path.find("frameworks/support/samples/SupportLeanbackShowcase")
-if index < 0:
-  print "ERROR: Invalid repo {0}".format(current_path)
-  exit(0)
-
-repo_path = current_path[:index]
-support_frameworks_path = repo_path + "/frameworks/support"
-if not (os.path.isdir(repo_path) or os.path.isdir(support_frameworks_path)):
-  print 'ERROR : Repo "{0}" does not exist'.format(repo_path)
-  print 'Please run gradlew uploadArchives inside frameworks/support'
-  exit(0)
-
-# Substitute LIBRARY_VERSION/LOCAL_REPO in local.properties
-library_version = lookup_local_library_version(repo_path)
-replace(os.getcwd()+"/local.properties", r'(.*)LOCAL_REPO(.*)', 'LOCAL_REPO='+repo_path)
-replace(os.getcwd()+"/local.properties", r'(.*)LIBRARY_VERSION(.*)', 'LIBRARY_VERSION='+library_version)
-
-# Build
-print "Building SupportLeanbackShowcase app..."
-subprocess.call(["./gradlew", "assembleDebug"])
-
-#Install apk
-print "Installing SupportLeanbackShowcase..."
-subprocess.call(["adb", "install", "-r", "./app/build/outputs/apk/app-debug.apk"])
-
-print "Finished installing SupportLeanbackShowcase app."
-
diff --git a/samples/SupportLeanbackShowcase/build-release.py b/samples/SupportLeanbackShowcase/build-release.py
deleted file mode 100644
index 1ee4ef3..0000000
--- a/samples/SupportLeanbackShowcase/build-release.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# BUILDING SupportLeanbackShowcase app using local library.
-import sys
-import subprocess
-import os
-import fileinput
-import re
-
-# Does an inplace substitution of the pattern with newVal in inputFile
-def replace(inputFile, pattern, newVal, ):
-  print 'About to replace repo path to {0} in {1}'.format(newVal, inputFile)
-  replaced = False
-  if os.path.exists(inputFile):
-    for line in fileinput.input(inputFile, inplace = 1):
-      if re.match(pattern, line, re.I|re.M):
-        line = re.sub(pattern, newVal, line)
-        replaced = True
-      print line,
-
-  if not replaced:
-    file = open(inputFile, "a")
-    file.write(newVal + "\n")
-
-# Substitute LIBRARY_VERSION/LOCAL_REPO in local.properties
-# It will use default values in build.gradle
-replace(os.getcwd()+"/local.properties", r'(.*)LOCAL_REPO(.*)', 'LOCAL_REPO=')
-replace(os.getcwd()+"/local.properties", r'(.*)LIBRARY_VERSION(.*)', 'LIBRARY_VERSION=')
-
-# Build
-print "Building SupportLeanbackShowcase app..."
-subprocess.call(["./gradlew", "assembleDebug"])
-
-#Install apk
-print "Installing SupportLeanbackShowcase..."
-subprocess.call(["adb", "install", "-r", "./app/build/outputs/apk/app-debug.apk"])
-
-print "Finished installing SupportLeanbackShowcase app."
-
diff --git a/samples/SupportLeanbackShowcase/build.gradle b/samples/SupportLeanbackShowcase/build.gradle
deleted file mode 100644
index 74f1e76..0000000
--- a/samples/SupportLeanbackShowcase/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-ext {
-  // This will be set by local.properties file. By default it
-  // will use the public release version (24.0.0). You can run
-  // python build-local.py to build the local verison. That script will
-  // figure out the local library version based on maven metadata for leanback
-  // library and update local.properties file. Gradle build file in turn
-  // will pick up the settings from local.properties.
-  Properties properties = new Properties()
-  properties.load(project.rootProject.file('local.properties').newDataInputStream())
-  supportLibVersion = properties.getProperty('LIBRARY_VERSION')
-  supportLibVersion = supportLibVersion ? supportLibVersion : "24.0.0"
-  localRepo = properties.getProperty('LOCAL_REPO')
-}
-
-buildscript {
-    repositories {
-        jcenter()
-    }
-    dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.1'
-
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
-    }
-}
-
-allprojects {
-    repositories {
-        maven { url "${localRepo}/out/host/gradle/frameworks/support/build/support_repo/"}
-        jcenter()
-    }
-}
diff --git a/samples/SupportLeanbackShowcase/gradle.properties b/samples/SupportLeanbackShowcase/gradle.properties
deleted file mode 100644
index 1d3591c..0000000
--- a/samples/SupportLeanbackShowcase/gradle.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
diff --git a/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.jar b/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 8c0fb64..0000000
--- a/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.properties b/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 5686dee..0000000
--- a/samples/SupportLeanbackShowcase/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Thu Sep 01 17:18:32 PDT 2016
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/samples/SupportLeanbackShowcase/gradlew b/samples/SupportLeanbackShowcase/gradlew
deleted file mode 100755
index 91a7e26..0000000
--- a/samples/SupportLeanbackShowcase/gradlew
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# 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
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# 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\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
-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" ] ; 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"`
-
-    # 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
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/samples/SupportLeanbackShowcase/gradlew.bat b/samples/SupportLeanbackShowcase/gradlew.bat
deleted file mode 100644
index aec9973..0000000
--- a/samples/SupportLeanbackShowcase/gradlew.bat
+++ /dev/null
@@ -1,90 +0,0 @@
-@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

-

-@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=

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@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 Windowz variants

-

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

-if "%@eval[2+2]" == "4" goto 4NT_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=%*

-goto execute

-

-:4NT_args

-@rem Get arguments from the 4NT Shell from JP Software

-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/samples/SupportLeanbackShowcase/libs/gson-1.7.2.jar b/samples/SupportLeanbackShowcase/libs/gson-1.7.2.jar
deleted file mode 100644
index 99e7afc..0000000
--- a/samples/SupportLeanbackShowcase/libs/gson-1.7.2.jar
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/libs/picasso-2.5.2.jar b/samples/SupportLeanbackShowcase/libs/picasso-2.5.2.jar
deleted file mode 100644
index 6acbaa1..0000000
--- a/samples/SupportLeanbackShowcase/libs/picasso-2.5.2.jar
+++ /dev/null
Binary files differ
diff --git a/samples/SupportLeanbackShowcase/settings.gradle b/samples/SupportLeanbackShowcase/settings.gradle
deleted file mode 100644
index e7b4def..0000000
--- a/samples/SupportLeanbackShowcase/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
index eb9a4c0..5a6e996 100644
--- a/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
@@ -16,10 +16,10 @@
 
 package com.example.android.supportpreference;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.Fragment;
 import android.os.Bundle;
+import android.support.annotation.RequiresApi;
 import android.support.v14.preference.PreferenceDialogFragment;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v17.preference.LeanbackPreferenceFragment;
@@ -27,7 +27,7 @@
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
 
-@TargetApi(17)
+@RequiresApi(17)
 public class FragmentSupportPreferencesLeanback extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
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/samples/ViewPager2Demos/build.gradle b/samples/ViewPager2Demos/build.gradle
new file mode 100644
index 0000000..04b831a
--- /dev/null
+++ b/samples/ViewPager2Demos/build.gradle
@@ -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.
+ */
+
+apply plugin: 'com.android.application'
+
+dependencies {
+    implementation(project(":viewpager2"))
+}
+
+android {
+    compileSdkVersion project.ext.currentSdk
+
+    defaultConfig {
+        minSdkVersion 14
+        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/ViewPager2Demos/src/main/AndroidManifest.xml b/samples/ViewPager2Demos/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..57e52d3
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/AndroidManifest.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.
+-->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.androidx.widget.viewpager2">
+
+    <application
+        android:icon="@drawable/app_sample_code"
+        android:label="@string/activity_sample_code"
+        android:supportsRtl="true"
+        android:theme="@style/android:Theme.Holo.Light">
+
+        <activity android:name=".CardActivity" android:label="CardsDemo">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".BrowseActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java
new file mode 100644
index 0000000..6c36994
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/BrowseActivity.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.example.androidx.widget.viewpager2;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This activity lists all the activities in this application.
+ *
+ * TODO: consider pulling out into a common test / demo library (a few forks of this class around)
+ */
+public class BrowseActivity extends ListActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        String path = intent.getStringExtra("com.example.androidx.widget.viewpager2");
+
+        if (path == null) {
+            path = "";
+        }
+
+        setListAdapter(new SimpleAdapter(this, getData(path),
+                android.R.layout.simple_list_item_1, new String[]{"title"},
+                new int[]{android.R.id.text1}));
+        getListView().setTextFilterEnabled(true);
+    }
+
+    protected List<Map<String, Object>> getData(String prefix) {
+        List<Map<String, Object>> myData = new ArrayList<>();
+
+        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+        mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE);
+
+        PackageManager pm = getPackageManager();
+        List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);
+
+        if (null == list) {
+            return myData;
+        }
+
+        String[] prefixPath;
+        String prefixWithSlash = prefix;
+
+        if (prefix.equals("")) {
+            prefixPath = null;
+        } else {
+            prefixPath = prefix.split("/");
+            prefixWithSlash = prefix + "/";
+        }
+
+        int len = list.size();
+
+        Map<String, Boolean> entries = new HashMap<>();
+
+        for (int i = 0; i < len; i++) {
+            ResolveInfo info = list.get(i);
+            CharSequence labelSeq = info.loadLabel(pm);
+            String label = labelSeq != null ? labelSeq.toString() : info.activityInfo.name;
+
+            if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) {
+
+                String[] labelPath = label.split("/");
+
+                String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length];
+
+                if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) {
+                    addItem(myData, nextLabel, activityIntent(
+                            info.activityInfo.applicationInfo.packageName,
+                            info.activityInfo.name));
+                } else {
+                    if (entries.get(nextLabel) == null) {
+                        addItem(myData, nextLabel, browseIntent(prefix.equals("")
+                                ? nextLabel : prefix + "/" + nextLabel));
+                        entries.put(nextLabel, true);
+                    }
+                }
+            }
+        }
+
+        Collections.sort(myData, sDisplayNameComparator);
+        return myData;
+    }
+
+    private static final Comparator<Map<String, Object>> sDisplayNameComparator =
+            new Comparator<Map<String, Object>>() {
+                final Collator mCollator = Collator.getInstance();
+
+                public int compare(Map<String, Object> map1, Map<String, Object> map2) {
+                    return mCollator.compare(map1.get("title"), map2.get("title"));
+                }
+            };
+
+    protected Intent activityIntent(String pkg, String componentName) {
+        Intent result = new Intent();
+        result.setClassName(pkg, componentName);
+        return result;
+    }
+
+    protected Intent browseIntent(String path) {
+        Intent result = new Intent();
+        result.setClass(this, BrowseActivity.class);
+        result.putExtra("com.example.androidx.widget.viewpager2", path);
+        return result;
+    }
+
+    protected void addItem(List<Map<String, Object>> data, String name, Intent intent) {
+        Map<String, Object> temp = new HashMap<>();
+        temp.put("title", name);
+        temp.put("intent", intent);
+        data.add(temp);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Map<String, Object> map = (Map<String, Object>) l.getItemAtPosition(position);
+
+        Intent intent = new Intent((Intent) map.get("intent"));
+        intent.addCategory(Intent.CATEGORY_SAMPLE_CODE);
+        startActivity(intent);
+    }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java
new file mode 100644
index 0000000..7484389
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/CardActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.example.androidx.widget.viewpager2;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.example.androidx.widget.viewpager2.cards.Card;
+import com.example.androidx.widget.viewpager2.cards.CardDataAdapter;
+
+import java.util.List;
+
+import androidx.widget.ViewPager2;
+
+/** @inheritDoc */
+public class CardActivity extends Activity {
+    private static final List<Card> sCards = unmodifiableList(asList(
+            new Card('♦', 'A'),
+            new Card('♣', 'K'),
+            new Card('♥', 'J'),
+            new Card('♠', '9'),
+            new Card('♦', '2')));
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.activity_card_layout);
+
+        this.<ViewPager2>findViewById(R.id.view_pager).setAdapter(
+                new CardDataAdapter(getLayoutInflater(), sCards));
+    }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java
new file mode 100644
index 0000000..02f3271
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/Card.java
@@ -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 com.example.androidx.widget.viewpager2.cards;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableSet;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Playing card
+ */
+public class Card {
+    private static final Set<Character> SUITS = unmodifiableSet(new LinkedHashSet<>(
+            asList('♦', /* diamonds*/ '♣', /*, clubs*/ '♥', /* hearts*/ '♠' /*spades*/)));
+    private static final Set<Character> VALUES = unmodifiableSet(new LinkedHashSet<>(
+            asList('2', '3', '4', '5', '6', '7', '8', '9', 'â’‘', 'J', 'Q', 'K', 'A')));
+
+    private final char mSuit;
+    private final char mValue;
+
+    public Card(char suit, char value) {
+        this.mSuit = checkValidValue(suit, SUITS);
+        this.mValue = checkValidValue(value, VALUES);
+    }
+
+    public char getSuit() {
+        return mSuit;
+    }
+
+    public String getCornerLabel() {
+        return mValue + "\n" + mSuit;
+    }
+
+    private static char checkValidValue(char value, Set<Character> allowed) {
+        if (allowed.contains(value)) {
+            return value;
+        }
+        throw new IllegalArgumentException("Illegal argument: " + value);
+    }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java
new file mode 100644
index 0000000..7f69e1b
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardDataAdapter.java
@@ -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 com.example.androidx.widget.viewpager2.cards;
+
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.example.androidx.widget.viewpager2.R;
+
+import java.util.List;
+
+/** @inheritDoc */
+public class CardDataAdapter extends RecyclerView.Adapter<CardViewHolder> {
+    private final List<Card> mCards;
+    private final LayoutInflater mLayoutInflater;
+
+    public CardDataAdapter(LayoutInflater layoutInflater, List<Card> cards) {
+        mLayoutInflater = layoutInflater;
+        mCards = cards;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @NonNull
+    public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        return new CardViewHolder(
+                mLayoutInflater.inflate(R.layout.item_card_layout, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull CardViewHolder holder, int position) {
+        holder.apply(mCards.get(position));
+    }
+
+    @Override
+    public int getItemCount() {
+        return mCards.size();
+    }
+}
diff --git a/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java
new file mode 100644
index 0000000..8fd0477
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/java/com/example/androidx/widget/viewpager2/cards/CardViewHolder.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.example.androidx.widget.viewpager2.cards;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+import com.example.androidx.widget.viewpager2.R;
+
+/** @inheritDoc */
+public class CardViewHolder extends RecyclerView.ViewHolder {
+    private final TextView mTextSuite;
+    private final TextView mTextCorner1;
+    private final TextView mTextCorner2;
+
+    public CardViewHolder(View itemView) {
+        super(itemView);
+        mTextSuite = itemView.findViewById(R.id.label_center);
+        mTextCorner1 = itemView.findViewById(R.id.label_top);
+        mTextCorner2 = itemView.findViewById(R.id.label_bottom);
+    }
+
+    /**
+     * Updates the view to represent the passed in card
+     */
+    public void apply(Card card) {
+        mTextSuite.setText(Character.toString(card.getSuit()));
+
+        String cornerLabel = card.getCornerLabel();
+        mTextCorner1.setText(cornerLabel);
+        mTextCorner2.setText(cornerLabel);
+        mTextCorner2.setRotation(180);
+    }
+}
diff --git a/samples/ViewPager2Demos/src/main/res/drawable-hdpi/app_sample_code.png b/samples/ViewPager2Demos/src/main/res/drawable-hdpi/app_sample_code.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/res/drawable-hdpi/app_sample_code.png
Binary files differ
diff --git a/samples/ViewPager2Demos/src/main/res/drawable-mdpi/app_sample_code.png b/samples/ViewPager2Demos/src/main/res/drawable-mdpi/app_sample_code.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/res/drawable-mdpi/app_sample_code.png
Binary files differ
diff --git a/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml b/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.xml
new file mode 100644
index 0000000..3037029
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/res/layout/activity_card_layout.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.
+-->
+
+<androidx.widget.ViewPager2
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/view_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFF"
+    android:padding="16dp"/>
diff --git a/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml b/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml
new file mode 100644
index 0000000..90a0404
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/res/layout/item_card_layout.xml
@@ -0,0 +1,44 @@
+<?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:background="#FEFEFE"
+    android:padding="16dp">
+
+    <TextView
+        android:id="@+id/label_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|start"
+        android:textAppearance="@android:style/TextAppearance.Medium"/>
+
+    <TextView
+        android:id="@+id/label_center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textAppearance="@android:style/TextAppearance.Large"/>
+
+    <TextView
+        android:id="@+id/label_bottom"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:textAppearance="@android:style/TextAppearance.Medium"/>
+</FrameLayout>
diff --git a/samples/ViewPager2Demos/src/main/res/values/strings.xml b/samples/ViewPager2Demos/src/main/res/values/strings.xml
new file mode 100644
index 0000000..85b9151
--- /dev/null
+++ b/samples/ViewPager2Demos/src/main/res/values/strings.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.
+-->
+
+<resources>
+    <string name="activity_sample_code">ViewPager2 Demos</string>
+</resources>
diff --git a/settings.gradle b/settings.gradle
index c281bb1..7b6c30d 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')
@@ -82,6 +85,9 @@
 include ':support-dynamic-animation'
 project(':support-dynamic-animation').projectDir = new File(rootDir, 'dynamic-animation')
 
+include ':viewpager2'
+project(':viewpager2').projectDir = new File(rootDir, 'viewpager2')
+
 include ':exifinterface'
 project(':exifinterface').projectDir = new File(rootDir, 'exifinterface')
 
@@ -103,6 +109,15 @@
 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')
+
+include ':webkit-codegen'
+project(':webkit-codegen').projectDir = new File(rootDir, 'webkit-codegen')
+
 /////////////////////////////
 //
 // Samples
@@ -147,6 +162,9 @@
 include ':support-animation-demos'
 project(':support-animation-demos').projectDir = new File(samplesRoot, 'SupportAnimationDemos')
 
+include ':viewpager2-demos'
+project(':viewpager2-demos').projectDir = new File(samplesRoot, 'ViewPager2Demos')
+
 include ':support-wear-demos'
 project(':support-wear-demos').projectDir = new File(samplesRoot, 'SupportWearDemos')
 
@@ -156,6 +174,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 +192,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 e79f70a..6a340b0 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..466cfa8 100644
--- a/transition/Android.mk
+++ b/transition/Android.mk
@@ -27,16 +27,12 @@
 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)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+    $(call all-java-files-under,src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-v4
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
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 3d907e2..d8257ab 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"))
@@ -20,22 +20,6 @@
 }
 
 android {
-    sourceSets {
-        main.java.srcDirs = [
-                'base',
-                'api14',
-                'api18',
-                'api19',
-                'api21',
-                'api22',
-                'src'
-        ]
-        main.res.srcDirs = [
-                'res',
-                'res-public'
-        ]
-    }
-
     buildTypes.all {
         consumerProguardFiles 'proguard-rules.pro'
     }
@@ -52,5 +36,4 @@
     mavenGroup = LibraryGroups.SUPPORT
     inceptionYear = "2016"
     description = "Android Transition Support Library"
-    legacySourceLocation = true
 }
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/tests/AndroidManifest.xml b/transition/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from transition/tests/AndroidManifest.xml
rename to transition/src/androidTest/AndroidManifest.xml
diff --git a/transition/tests/NO_DOCS b/transition/src/androidTest/NO_DOCS
similarity index 100%
rename from transition/tests/NO_DOCS
rename to transition/src/androidTest/NO_DOCS
diff --git a/transition/tests/src/android/support/transition/ArcMotionTest.java b/transition/src/androidTest/java/android/support/transition/ArcMotionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ArcMotionTest.java
rename to transition/src/androidTest/java/android/support/transition/ArcMotionTest.java
diff --git a/transition/tests/src/android/support/transition/AutoTransitionTest.java b/transition/src/androidTest/java/android/support/transition/AutoTransitionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/AutoTransitionTest.java
rename to transition/src/androidTest/java/android/support/transition/AutoTransitionTest.java
diff --git a/transition/tests/src/android/support/transition/BaseTest.java b/transition/src/androidTest/java/android/support/transition/BaseTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/BaseTest.java
rename to transition/src/androidTest/java/android/support/transition/BaseTest.java
diff --git a/transition/tests/src/android/support/transition/BaseTransitionTest.java b/transition/src/androidTest/java/android/support/transition/BaseTransitionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/BaseTransitionTest.java
rename to transition/src/androidTest/java/android/support/transition/BaseTransitionTest.java
diff --git a/transition/tests/src/android/support/transition/ChangeBoundsTest.java b/transition/src/androidTest/java/android/support/transition/ChangeBoundsTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ChangeBoundsTest.java
rename to transition/src/androidTest/java/android/support/transition/ChangeBoundsTest.java
diff --git a/transition/tests/src/android/support/transition/ChangeClipBoundsTest.java b/transition/src/androidTest/java/android/support/transition/ChangeClipBoundsTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ChangeClipBoundsTest.java
rename to transition/src/androidTest/java/android/support/transition/ChangeClipBoundsTest.java
diff --git a/transition/tests/src/android/support/transition/ChangeImageTransformTest.java b/transition/src/androidTest/java/android/support/transition/ChangeImageTransformTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ChangeImageTransformTest.java
rename to transition/src/androidTest/java/android/support/transition/ChangeImageTransformTest.java
diff --git a/transition/tests/src/android/support/transition/ChangeScrollTest.java b/transition/src/androidTest/java/android/support/transition/ChangeScrollTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ChangeScrollTest.java
rename to transition/src/androidTest/java/android/support/transition/ChangeScrollTest.java
diff --git a/transition/tests/src/android/support/transition/ChangeTransformTest.java b/transition/src/androidTest/java/android/support/transition/ChangeTransformTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ChangeTransformTest.java
rename to transition/src/androidTest/java/android/support/transition/ChangeTransformTest.java
diff --git a/transition/tests/src/android/support/transition/CheckCalledRunnable.java b/transition/src/androidTest/java/android/support/transition/CheckCalledRunnable.java
similarity index 100%
rename from transition/tests/src/android/support/transition/CheckCalledRunnable.java
rename to transition/src/androidTest/java/android/support/transition/CheckCalledRunnable.java
diff --git a/transition/tests/src/android/support/transition/ExplodeTest.java b/transition/src/androidTest/java/android/support/transition/ExplodeTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/ExplodeTest.java
rename to transition/src/androidTest/java/android/support/transition/ExplodeTest.java
diff --git a/transition/tests/src/android/support/transition/FadeTest.java b/transition/src/androidTest/java/android/support/transition/FadeTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/FadeTest.java
rename to transition/src/androidTest/java/android/support/transition/FadeTest.java
diff --git a/transition/tests/src/android/support/transition/FragmentTransitionTest.java b/transition/src/androidTest/java/android/support/transition/FragmentTransitionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/FragmentTransitionTest.java
rename to transition/src/androidTest/java/android/support/transition/FragmentTransitionTest.java
diff --git a/transition/tests/src/android/support/transition/PathMotionTest.java b/transition/src/androidTest/java/android/support/transition/PathMotionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/PathMotionTest.java
rename to transition/src/androidTest/java/android/support/transition/PathMotionTest.java
diff --git a/transition/tests/src/android/support/transition/PatternPathMotionTest.java b/transition/src/androidTest/java/android/support/transition/PatternPathMotionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/PatternPathMotionTest.java
rename to transition/src/androidTest/java/android/support/transition/PatternPathMotionTest.java
diff --git a/transition/tests/src/android/support/transition/PropagationTest.java b/transition/src/androidTest/java/android/support/transition/PropagationTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/PropagationTest.java
rename to transition/src/androidTest/java/android/support/transition/PropagationTest.java
diff --git a/transition/tests/src/android/support/transition/SceneTest.java b/transition/src/androidTest/java/android/support/transition/SceneTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SceneTest.java
rename to transition/src/androidTest/java/android/support/transition/SceneTest.java
diff --git a/transition/tests/src/android/support/transition/SlideBadEdgeTest.java b/transition/src/androidTest/java/android/support/transition/SlideBadEdgeTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SlideBadEdgeTest.java
rename to transition/src/androidTest/java/android/support/transition/SlideBadEdgeTest.java
diff --git a/transition/tests/src/android/support/transition/SlideDefaultEdgeTest.java b/transition/src/androidTest/java/android/support/transition/SlideDefaultEdgeTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SlideDefaultEdgeTest.java
rename to transition/src/androidTest/java/android/support/transition/SlideDefaultEdgeTest.java
diff --git a/transition/tests/src/android/support/transition/SlideEdgeTest.java b/transition/src/androidTest/java/android/support/transition/SlideEdgeTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SlideEdgeTest.java
rename to transition/src/androidTest/java/android/support/transition/SlideEdgeTest.java
diff --git a/transition/tests/src/android/support/transition/SyncRunnable.java b/transition/src/androidTest/java/android/support/transition/SyncRunnable.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SyncRunnable.java
rename to transition/src/androidTest/java/android/support/transition/SyncRunnable.java
diff --git a/transition/tests/src/android/support/transition/SyncTransitionListener.java b/transition/src/androidTest/java/android/support/transition/SyncTransitionListener.java
similarity index 100%
rename from transition/tests/src/android/support/transition/SyncTransitionListener.java
rename to transition/src/androidTest/java/android/support/transition/SyncTransitionListener.java
diff --git a/transition/tests/src/android/support/transition/TransitionActivity.java b/transition/src/androidTest/java/android/support/transition/TransitionActivity.java
similarity index 100%
rename from transition/tests/src/android/support/transition/TransitionActivity.java
rename to transition/src/androidTest/java/android/support/transition/TransitionActivity.java
diff --git a/transition/tests/src/android/support/transition/TransitionInflaterTest.java b/transition/src/androidTest/java/android/support/transition/TransitionInflaterTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/TransitionInflaterTest.java
rename to transition/src/androidTest/java/android/support/transition/TransitionInflaterTest.java
diff --git a/transition/tests/src/android/support/transition/TransitionManagerTest.java b/transition/src/androidTest/java/android/support/transition/TransitionManagerTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/TransitionManagerTest.java
rename to transition/src/androidTest/java/android/support/transition/TransitionManagerTest.java
diff --git a/transition/tests/src/android/support/transition/TransitionSetTest.java b/transition/src/androidTest/java/android/support/transition/TransitionSetTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/TransitionSetTest.java
rename to transition/src/androidTest/java/android/support/transition/TransitionSetTest.java
diff --git a/transition/tests/src/android/support/transition/TransitionTest.java b/transition/src/androidTest/java/android/support/transition/TransitionTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/TransitionTest.java
rename to transition/src/androidTest/java/android/support/transition/TransitionTest.java
diff --git a/transition/tests/src/android/support/transition/VisibilityTest.java b/transition/src/androidTest/java/android/support/transition/VisibilityTest.java
similarity index 100%
rename from transition/tests/src/android/support/transition/VisibilityTest.java
rename to transition/src/androidTest/java/android/support/transition/VisibilityTest.java
diff --git a/transition/tests/res/layout/activity_transition.xml b/transition/src/androidTest/res/layout/activity_transition.xml
similarity index 100%
rename from transition/tests/res/layout/activity_transition.xml
rename to transition/src/androidTest/res/layout/activity_transition.xml
diff --git a/transition/tests/res/layout/scene1.xml b/transition/src/androidTest/res/layout/scene1.xml
similarity index 100%
rename from transition/tests/res/layout/scene1.xml
rename to transition/src/androidTest/res/layout/scene1.xml
diff --git a/transition/tests/res/layout/scene10.xml b/transition/src/androidTest/res/layout/scene10.xml
similarity index 100%
rename from transition/tests/res/layout/scene10.xml
rename to transition/src/androidTest/res/layout/scene10.xml
diff --git a/transition/tests/res/layout/scene2.xml b/transition/src/androidTest/res/layout/scene2.xml
similarity index 100%
rename from transition/tests/res/layout/scene2.xml
rename to transition/src/androidTest/res/layout/scene2.xml
diff --git a/transition/tests/res/layout/scene3.xml b/transition/src/androidTest/res/layout/scene3.xml
similarity index 100%
rename from transition/tests/res/layout/scene3.xml
rename to transition/src/androidTest/res/layout/scene3.xml
diff --git a/transition/tests/res/layout/scene4.xml b/transition/src/androidTest/res/layout/scene4.xml
similarity index 100%
rename from transition/tests/res/layout/scene4.xml
rename to transition/src/androidTest/res/layout/scene4.xml
diff --git a/transition/tests/res/layout/scene5.xml b/transition/src/androidTest/res/layout/scene5.xml
similarity index 100%
rename from transition/tests/res/layout/scene5.xml
rename to transition/src/androidTest/res/layout/scene5.xml
diff --git a/transition/tests/res/layout/scene6.xml b/transition/src/androidTest/res/layout/scene6.xml
similarity index 100%
rename from transition/tests/res/layout/scene6.xml
rename to transition/src/androidTest/res/layout/scene6.xml
diff --git a/transition/tests/res/layout/scene9.xml b/transition/src/androidTest/res/layout/scene9.xml
similarity index 100%
rename from transition/tests/res/layout/scene9.xml
rename to transition/src/androidTest/res/layout/scene9.xml
diff --git a/transition/tests/res/layout/support_scene0.xml b/transition/src/androidTest/res/layout/support_scene0.xml
similarity index 100%
rename from transition/tests/res/layout/support_scene0.xml
rename to transition/src/androidTest/res/layout/support_scene0.xml
diff --git a/transition/tests/res/layout/support_scene1.xml b/transition/src/androidTest/res/layout/support_scene1.xml
similarity index 100%
rename from transition/tests/res/layout/support_scene1.xml
rename to transition/src/androidTest/res/layout/support_scene1.xml
diff --git a/transition/tests/res/transition/arc_motion.xml b/transition/src/androidTest/res/transition/arc_motion.xml
similarity index 100%
rename from transition/tests/res/transition/arc_motion.xml
rename to transition/src/androidTest/res/transition/arc_motion.xml
diff --git a/transition/tests/res/transition/auto_transition.xml b/transition/src/androidTest/res/transition/auto_transition.xml
similarity index 100%
rename from transition/tests/res/transition/auto_transition.xml
rename to transition/src/androidTest/res/transition/auto_transition.xml
diff --git a/transition/tests/res/transition/change_bounds.xml b/transition/src/androidTest/res/transition/change_bounds.xml
similarity index 100%
rename from transition/tests/res/transition/change_bounds.xml
rename to transition/src/androidTest/res/transition/change_bounds.xml
diff --git a/transition/tests/res/transition/change_clip_bounds.xml b/transition/src/androidTest/res/transition/change_clip_bounds.xml
similarity index 100%
rename from transition/tests/res/transition/change_clip_bounds.xml
rename to transition/src/androidTest/res/transition/change_clip_bounds.xml
diff --git a/transition/tests/res/transition/change_image_transform.xml b/transition/src/androidTest/res/transition/change_image_transform.xml
similarity index 100%
rename from transition/tests/res/transition/change_image_transform.xml
rename to transition/src/androidTest/res/transition/change_image_transform.xml
diff --git a/transition/tests/res/transition/change_scroll.xml b/transition/src/androidTest/res/transition/change_scroll.xml
similarity index 100%
rename from transition/tests/res/transition/change_scroll.xml
rename to transition/src/androidTest/res/transition/change_scroll.xml
diff --git a/transition/tests/res/transition/change_transform.xml b/transition/src/androidTest/res/transition/change_transform.xml
similarity index 100%
rename from transition/tests/res/transition/change_transform.xml
rename to transition/src/androidTest/res/transition/change_transform.xml
diff --git a/transition/tests/res/transition/custom_path_motion.xml b/transition/src/androidTest/res/transition/custom_path_motion.xml
similarity index 100%
rename from transition/tests/res/transition/custom_path_motion.xml
rename to transition/src/androidTest/res/transition/custom_path_motion.xml
diff --git a/transition/tests/res/transition/custom_transition.xml b/transition/src/androidTest/res/transition/custom_transition.xml
similarity index 100%
rename from transition/tests/res/transition/custom_transition.xml
rename to transition/src/androidTest/res/transition/custom_transition.xml
diff --git a/transition/tests/res/transition/explode.xml b/transition/src/androidTest/res/transition/explode.xml
similarity index 100%
rename from transition/tests/res/transition/explode.xml
rename to transition/src/androidTest/res/transition/explode.xml
diff --git a/transition/tests/res/transition/fade.xml b/transition/src/androidTest/res/transition/fade.xml
similarity index 100%
rename from transition/tests/res/transition/fade.xml
rename to transition/src/androidTest/res/transition/fade.xml
diff --git a/transition/tests/res/transition/pattern_path_motion.xml b/transition/src/androidTest/res/transition/pattern_path_motion.xml
similarity index 100%
rename from transition/tests/res/transition/pattern_path_motion.xml
rename to transition/src/androidTest/res/transition/pattern_path_motion.xml
diff --git a/transition/tests/res/transition/slide.xml b/transition/src/androidTest/res/transition/slide.xml
similarity index 100%
rename from transition/tests/res/transition/slide.xml
rename to transition/src/androidTest/res/transition/slide.xml
diff --git a/transition/tests/res/transition/target_classes.xml b/transition/src/androidTest/res/transition/target_classes.xml
similarity index 100%
rename from transition/tests/res/transition/target_classes.xml
rename to transition/src/androidTest/res/transition/target_classes.xml
diff --git a/transition/tests/res/transition/target_ids.xml b/transition/src/androidTest/res/transition/target_ids.xml
similarity index 100%
rename from transition/tests/res/transition/target_ids.xml
rename to transition/src/androidTest/res/transition/target_ids.xml
diff --git a/transition/tests/res/transition/target_names.xml b/transition/src/androidTest/res/transition/target_names.xml
similarity index 100%
rename from transition/tests/res/transition/target_names.xml
rename to transition/src/androidTest/res/transition/target_names.xml
diff --git a/transition/tests/res/transition/transition_constructors.xml b/transition/src/androidTest/res/transition/transition_constructors.xml
similarity index 100%
rename from transition/tests/res/transition/transition_constructors.xml
rename to transition/src/androidTest/res/transition/transition_constructors.xml
diff --git a/transition/tests/res/transition/transition_set.xml b/transition/src/androidTest/res/transition/transition_set.xml
similarity index 100%
rename from transition/tests/res/transition/transition_set.xml
rename to transition/src/androidTest/res/transition/transition_set.xml
diff --git a/transition/tests/res/values/strings.xml b/transition/src/androidTest/res/values/strings.xml
similarity index 100%
rename from transition/tests/res/values/strings.xml
rename to transition/src/androidTest/res/values/strings.xml
diff --git a/transition/AndroidManifest.xml b/transition/src/main/AndroidManifest.xml
similarity index 100%
rename from transition/AndroidManifest.xml
rename to transition/src/main/AndroidManifest.xml
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/transition/res/values/ids.xml b/transition/src/main/res/values/ids.xml
similarity index 100%
rename from transition/res/values/ids.xml
rename to transition/src/main/res/values/ids.xml
diff --git a/tv-provider/Android.mk b/tv-provider/Android.mk
index 9427d0d..2368ad3 100644
--- a/tv-provider/Android.mk
+++ b/tv-provider/Android.mk
@@ -25,9 +25,11 @@
 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-compat \
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-compat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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 c7d7c8a..1c8443a 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)
 }
 
 supportLibrary {
@@ -20,6 +20,5 @@
     mavenGroup = LibraryGroups.SUPPORT
     inceptionYear = "2017"
     description = "Android Support Library for TV Provider"
-    legacySourceLocation = true
     minSdkVersion = 21
 }
\ No newline at end of file
diff --git a/tv-provider/lint-baseline.xml b/tv-provider/lint-baseline.xml
deleted file mode 100644
index 9814796..0000000
--- a/tv-provider/lint-baseline.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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>
diff --git a/tv-provider/tests/AndroidManifest.xml b/tv-provider/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from tv-provider/tests/AndroidManifest.xml
rename to tv-provider/src/androidTest/AndroidManifest.xml
diff --git a/tv-provider/tests/NO_DOCS b/tv-provider/src/androidTest/NO_DOCS
similarity index 100%
rename from tv-provider/tests/NO_DOCS
rename to tv-provider/src/androidTest/NO_DOCS
diff --git a/tv-provider/tests/src/android/support/media/tv/ChannelLogoUtilsTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/ChannelLogoUtilsTest.java
similarity index 100%
rename from tv-provider/tests/src/android/support/media/tv/ChannelLogoUtilsTest.java
rename to tv-provider/src/androidTest/java/android/support/media/tv/ChannelLogoUtilsTest.java
diff --git a/tv-provider/tests/src/android/support/media/tv/ChannelTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/ChannelTest.java
similarity index 100%
rename from tv-provider/tests/src/android/support/media/tv/ChannelTest.java
rename to tv-provider/src/androidTest/java/android/support/media/tv/ChannelTest.java
diff --git a/tv-provider/src/androidTest/java/android/support/media/tv/PreviewProgramTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewProgramTest.java
new file mode 100644
index 0000000..5b1b717
--- /dev/null
+++ b/tv-provider/src/androidTest/java/android/support/media/tv/PreviewProgramTest.java
@@ -0,0 +1,381 @@
+/*
+ * 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.media.tv;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.media.tv.TvContentRating;
+import android.net.Uri;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Tests that preview programs can be created using the Builder pattern and correctly obtain
+ * values from them.
+ */
+@SmallTest
+@SdkSuppress(minSdkVersion = 26)
+public class PreviewProgramTest extends TestCase {
+
+    @Override
+    protected void tearDown() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(Channels.CONTENT_URI, null, null);
+    }
+
+    @Test
+    public void testEmptyPreviewProgram() {
+        PreviewProgram emptyProgram = new PreviewProgram.Builder().build();
+        ContentValues contentValues = emptyProgram.toContentValues();
+        compareProgram(emptyProgram,
+                PreviewProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
+                true);
+    }
+
+    @Test
+    public void testSampleProgram() {
+        PreviewProgram sampleProgram = new PreviewProgram.Builder()
+                .setPackageName("My package")
+                .setTitle("Program Title")
+                .setDescription("This is a sample program")
+                .setEpisodeNumber(5)
+                .setSeasonNumber("The Final Season", 7)
+                .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png"))
+                .setChannelId(3)
+                .setWeight(70)
+                .build();
+        ContentValues contentValues = sampleProgram.toContentValues(true);
+        compareProgram(sampleProgram,
+                PreviewProgram.fromCursor(
+                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
+
+        PreviewProgram clonedSampleProgram = new PreviewProgram.Builder(sampleProgram).build();
+        compareProgram(sampleProgram, clonedSampleProgram, true);
+    }
+
+    @Test
+    public void testFullyPopulatedPreviewProgram() {
+        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(3);
+        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
+        compareProgram(fullyPopulatedProgram,
+                PreviewProgram.fromCursor(
+                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
+
+        PreviewProgram clonedFullyPopulatedProgram =
+                new PreviewProgram.Builder(fullyPopulatedProgram).build();
+        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    @Test
+    public void testPreviewProgramWithSystemContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        Channel channel = new Channel.Builder()
+                .setInputId("TestInputService")
+                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
+                .build();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
+        assertNotNull(channelUri);
+
+        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(
+                ContentUris.parseId(channelUri));
+        Uri previewProgramUri = resolver.insert(PreviewPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        PreviewProgram programFromSystemDb =
+                loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
+    }
+
+    @Test
+    public void testPreviewProgramUpdateWithContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        Channel channel = new Channel.Builder()
+                .setInputId("TestInputService")
+                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
+                .build();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
+        assertNotNull(channelUri);
+
+        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(
+                ContentUris.parseId(channelUri));
+        Uri previewProgramUri = resolver.insert(PreviewPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        PreviewProgram programFromSystemDb = loadPreviewProgramFromContentProvider(
+                resolver, previewProgramUri);
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
+
+        // Update a field from a fully loaded preview program.
+        PreviewProgram updatedProgram = new PreviewProgram.Builder(programFromSystemDb)
+                .setInteractionCount(programFromSystemDb.getInteractionCount() + 1).build();
+        assertEquals(1, resolver.update(
+                previewProgramUri, updatedProgram.toContentValues(), null, null));
+        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
+        compareProgram(updatedProgram, programFromSystemDb, false);
+
+        // Update a field with null from a fully loaded preview program.
+        updatedProgram = new PreviewProgram.Builder(updatedProgram)
+                .setLongDescription(null).build();
+        assertEquals(1, resolver.update(
+                previewProgramUri, updatedProgram.toContentValues(), null, null));
+        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
+        compareProgram(updatedProgram, programFromSystemDb, false);
+
+        // Update a field without referencing fully loaded preview program.
+        ContentValues values = new PreviewProgram.Builder().setInteractionCount(1).build()
+                .toContentValues();
+        assertEquals(1, values.size());
+        assertEquals(1, resolver.update(previewProgramUri, values, null, null));
+        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
+        PreviewProgram expectedProgram = new PreviewProgram.Builder(programFromSystemDb)
+                .setInteractionCount(1).build();
+        compareProgram(expectedProgram, programFromSystemDb, false);
+    }
+
+    @Test
+    public void testPreviewProgramEquals() {
+        assertEquals(createFullyPopulatedPreviewProgram(1), createFullyPopulatedPreviewProgram(1));
+    }
+
+    private static PreviewProgram loadPreviewProgramFromContentProvider(
+            ContentResolver resolver, Uri previewProgramUri) {
+        try (Cursor cursor = resolver.query(previewProgramUri, null, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            return PreviewProgram.fromCursor(cursor);
+        }
+    }
+
+    @Test
+    public void testPreviewProgramWithPartialData() {
+        PreviewProgram previewProgram = new PreviewProgram.Builder()
+                .setChannelId(3)
+                .setWeight(100)
+                .setInternalProviderId("ID-4321")
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(PreviewPrograms.TYPE_TV_EPISODE)
+                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_3_2)
+                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(PreviewPrograms.AVAILABILITY_FREE_WITH_SUBSCRIPTION)
+                .setStartingPrice("9.99 USD")
+                .setOfferPrice("3.99 USD")
+                .setReleaseDate(new Date(Date.UTC(97, 2, 8, 9, 30, 59)))
+                .setLive(false)
+                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_VIEWS)
+                .setInteractionCount(99200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_PERCENTAGE)
+                .setReviewRating("83.9")
+                .setId(10)
+                .setTitle("Recommended Video 1")
+                .setDescription("You should watch this!")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setInternalProviderFlag2(0x0010010084108410L)
+                .build();
+
+        String[] partialProjection = {
+                PreviewPrograms._ID,
+                PreviewPrograms.COLUMN_CHANNEL_ID,
+                PreviewPrograms.COLUMN_TITLE,
+                PreviewPrograms.COLUMN_SHORT_DESCRIPTION,
+                PreviewPrograms.COLUMN_POSTER_ART_URI,
+                PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+                PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+                PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI,
+                PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+                PreviewPrograms.COLUMN_DURATION_MILLIS,
+                PreviewPrograms.COLUMN_INTENT_URI,
+                PreviewPrograms.COLUMN_WEIGHT,
+                PreviewPrograms.COLUMN_TRANSIENT,
+                PreviewPrograms.COLUMN_TYPE,
+                PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+                PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+                PreviewPrograms.COLUMN_LOGO_URI,
+                PreviewPrograms.COLUMN_AVAILABILITY,
+                PreviewPrograms.COLUMN_STARTING_PRICE,
+                PreviewPrograms.COLUMN_OFFER_PRICE,
+                PreviewPrograms.COLUMN_RELEASE_DATE,
+                PreviewPrograms.COLUMN_ITEM_COUNT,
+                PreviewPrograms.COLUMN_LIVE,
+                PreviewPrograms.COLUMN_INTERACTION_TYPE,
+                PreviewPrograms.COLUMN_INTERACTION_COUNT,
+                PreviewPrograms.COLUMN_AUTHOR,
+                PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
+                PreviewPrograms.COLUMN_REVIEW_RATING,
+        };
+
+        ContentValues contentValues = previewProgram.toContentValues(true);
+        compareProgram(previewProgram,
+                PreviewProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
+                true);
+
+        PreviewProgram clonedFullyPopulatedProgram =
+                new PreviewProgram.Builder(previewProgram).build();
+        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    private static PreviewProgram createFullyPopulatedPreviewProgram(long channelId) {
+        return new PreviewProgram.Builder()
+                .setTitle("Google")
+                .setInternalProviderId("ID-4321")
+                .setChannelId(channelId)
+                .setWeight(100)
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(PreviewPrograms.TYPE_MOVIE)
+                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_2_3)
+                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(PreviewPrograms.AVAILABILITY_AVAILABLE)
+                .setStartingPrice("12.99 USD")
+                .setOfferPrice("4.99 USD")
+                .setReleaseDate("1997")
+                .setItemCount(3)
+                .setLive(false)
+                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_LIKES)
+                .setInteractionCount(10200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_STARS)
+                .setReviewRating("4.5")
+                .setSearchable(false)
+                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+                .setAudioLanguages(new String [] {"eng", "kor"})
+                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
+                .setContentRatings(new TvContentRating[] {
+                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+                .setDescription("This is a sample program")
+                .setEpisodeNumber("Pilot", 0)
+                .setEpisodeTitle("Hello World")
+                .setLongDescription("This is a longer description than the previous description")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setSeasonNumber("The Final Season", 7)
+                .setSeasonTitle("The Final Season")
+                .setVideoHeight(1080)
+                .setVideoWidth(1920)
+                .setInternalProviderFlag1(0x4)
+                .setInternalProviderFlag2(0x3)
+                .setInternalProviderFlag3(0x2)
+                .setInternalProviderFlag4(0x1)
+                .setBrowsable(true)
+                .setContentId("CID-8642")
+                .build();
+    }
+
+    private static void compareProgram(PreviewProgram programA, PreviewProgram programB,
+            boolean includeIdAndProtectedFields) {
+        assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
+        assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
+        assertEquals(programA.getChannelId(), programB.getChannelId());
+        assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
+        assertEquals(programA.getDescription(), programB.getDescription());
+        assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
+        assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
+        assertEquals(programA.getLongDescription(), programB.getLongDescription());
+        assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
+        assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
+        assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
+        assertEquals(programA.getTitle(), programB.getTitle());
+        assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
+        assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
+        assertEquals(programA.isSearchable(), programB.isSearchable());
+        assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
+        assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
+        assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
+        assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
+        assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
+        assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
+        assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
+        assertEquals(programA.getLastPlaybackPositionMillis(),
+                programB.getLastPlaybackPositionMillis());
+        assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
+        assertEquals(programA.getIntentUri(), programB.getIntentUri());
+        assertEquals(programA.getWeight(), programB.getWeight());
+        assertEquals(programA.isTransient(), programB.isTransient());
+        assertEquals(programA.getType(), programB.getType());
+        assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
+        assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
+        assertEquals(programA.getLogoUri(), programB.getLogoUri());
+        assertEquals(programA.getAvailability(), programB.getAvailability());
+        assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
+        assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
+        assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
+        assertEquals(programA.getItemCount(), programB.getItemCount());
+        assertEquals(programA.isLive(), programB.isLive());
+        assertEquals(programA.getInteractionType(), programB.getInteractionType());
+        assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
+        assertEquals(programA.getAuthor(), programB.getAuthor());
+        assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
+        assertEquals(programA.getReviewRating(), programB.getReviewRating());
+        assertEquals(programA.getContentId(), programB.getContentId());
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(programA.getId(), programB.getId());
+            assertEquals(programA.getPackageName(), programB.getPackageName());
+            // When we insert a channel using toContentValues() to the system, we drop some
+            // protected fields since they only can be modified by system apps.
+            assertEquals(programA.isBrowsable(), programB.isBrowsable());
+            assertEquals(programA.toContentValues(), programB.toContentValues());
+            assertEquals(programA, programB);
+        }
+    }
+
+    private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
+        MatrixCursor cursor = new MatrixCursor(projection);
+        MatrixCursor.RowBuilder builder = cursor.newRow();
+        for (String col : projection) {
+            if (col != null) {
+                builder.add(col, contentValues.get(col));
+            }
+        }
+        cursor.moveToFirst();
+        return cursor;
+    }
+}
diff --git a/tv-provider/tests/src/android/support/media/tv/ProgramTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/ProgramTest.java
similarity index 100%
rename from tv-provider/tests/src/android/support/media/tv/ProgramTest.java
rename to tv-provider/src/androidTest/java/android/support/media/tv/ProgramTest.java
diff --git a/tv-provider/tests/src/android/support/media/tv/TvContractUtilsTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/TvContractUtilsTest.java
similarity index 100%
rename from tv-provider/tests/src/android/support/media/tv/TvContractUtilsTest.java
rename to tv-provider/src/androidTest/java/android/support/media/tv/TvContractUtilsTest.java
diff --git a/tv-provider/tests/src/android/support/media/tv/Utils.java b/tv-provider/src/androidTest/java/android/support/media/tv/Utils.java
similarity index 100%
rename from tv-provider/tests/src/android/support/media/tv/Utils.java
rename to tv-provider/src/androidTest/java/android/support/media/tv/Utils.java
diff --git a/tv-provider/src/androidTest/java/android/support/media/tv/WatchNextProgramTest.java b/tv-provider/src/androidTest/java/android/support/media/tv/WatchNextProgramTest.java
new file mode 100644
index 0000000..4e9c165
--- /dev/null
+++ b/tv-provider/src/androidTest/java/android/support/media/tv/WatchNextProgramTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.media.tv;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.media.tv.TvContentRating;
+import android.net.Uri;
+import android.support.media.tv.TvContractCompat.WatchNextPrograms;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Tests that watch next programs can be created using the Builder pattern and correctly obtain
+ * values from them.
+ */
+@SmallTest
+@SdkSuppress(minSdkVersion = 26)
+public class WatchNextProgramTest extends TestCase {
+
+    @Override
+    protected void tearDown() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(WatchNextPrograms.CONTENT_URI, null, null);
+    }
+
+    @Test
+    public void testEmptyPreviewProgram() {
+        WatchNextProgram emptyProgram = new WatchNextProgram.Builder().build();
+        ContentValues contentValues = emptyProgram.toContentValues(true);
+        compareProgram(emptyProgram,
+                WatchNextProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
+                true);
+    }
+
+    @Test
+    public void testSampleProgram() {
+        WatchNextProgram sampleProgram = new WatchNextProgram.Builder()
+                .setTitle("Program Title")
+                .setDescription("This is a sample program")
+                .setEpisodeNumber(5)
+                .setSeasonNumber("The Final Season", 7)
+                .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png"))
+                .build();
+        ContentValues contentValues = sampleProgram.toContentValues(true);
+        compareProgram(sampleProgram,
+                WatchNextProgram.fromCursor(
+                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
+
+        WatchNextProgram clonedSampleProgram = new WatchNextProgram.Builder(sampleProgram).build();
+        compareProgram(sampleProgram, clonedSampleProgram, true);
+    }
+
+    @Test
+    public void testFullyPopulatedProgram() {
+        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
+        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
+        compareProgram(fullyPopulatedProgram,
+                WatchNextProgram.fromCursor(
+                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
+
+        WatchNextProgram clonedFullyPopulatedProgram =
+                new WatchNextProgram.Builder(fullyPopulatedProgram).build();
+        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    @Test
+    public void testChannelWithSystemContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        WatchNextProgram programFromSystemDb =
+                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
+    }
+
+    @Test
+    public void testWatchNextProgramUpdateWithContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+
+        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        WatchNextProgram programFromSystemDb =
+                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
+
+        // Update a field from a fully loaded watch-next program.
+        WatchNextProgram updatedProgram = new WatchNextProgram.Builder(programFromSystemDb)
+                .setInteractionCount(programFromSystemDb.getInteractionCount() + 1).build();
+        assertEquals(1, resolver.update(
+                watchNextProgramUri, updatedProgram.toContentValues(), null, null));
+        programFromSystemDb =
+                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
+        compareProgram(updatedProgram, programFromSystemDb, false);
+
+        // Update a field with null from a fully loaded watch-next program.
+        updatedProgram = new WatchNextProgram.Builder(updatedProgram)
+                .setPreviewVideoUri(null).build();
+        assertEquals(1, resolver.update(
+                watchNextProgramUri, updatedProgram.toContentValues(), null, null));
+        programFromSystemDb = loadWatchNextProgramFromContentProvider(
+                resolver, watchNextProgramUri);
+        compareProgram(updatedProgram, programFromSystemDb, false);
+
+        // Update a field without referencing fully watch-next program.
+        ContentValues values = new PreviewProgram.Builder().setInteractionCount(1).build()
+                .toContentValues();
+        assertEquals(1, values.size());
+        assertEquals(1, resolver.update(watchNextProgramUri, values, null, null));
+        programFromSystemDb = loadWatchNextProgramFromContentProvider(
+                resolver, watchNextProgramUri);
+        WatchNextProgram expectedProgram = new WatchNextProgram.Builder(programFromSystemDb)
+                .setInteractionCount(1).build();
+        compareProgram(expectedProgram, programFromSystemDb, false);
+    }
+
+    @Test
+    public void testWatchNextProgramEquals() {
+        assertEquals(createFullyPopulatedWatchNextProgram(),
+                createFullyPopulatedWatchNextProgram());
+    }
+
+    private static WatchNextProgram loadWatchNextProgramFromContentProvider(
+            ContentResolver resolver, Uri watchNextProgramUri) {
+        try (Cursor cursor = resolver.query(watchNextProgramUri, null, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            return WatchNextProgram.fromCursor(cursor);
+        }
+    }
+
+    @Test
+    public void testWatchNextProgramWithPartialData() {
+        WatchNextProgram previewProgram = new WatchNextProgram.Builder()
+                .setInternalProviderId("ID-4321")
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(WatchNextPrograms.TYPE_TV_EPISODE)
+                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_3_2)
+                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(WatchNextPrograms.AVAILABILITY_FREE_WITH_SUBSCRIPTION)
+                .setStartingPrice("9.99 USD")
+                .setOfferPrice("3.99 USD")
+                .setReleaseDate(new Date(97, 2, 8))
+                .setLive(false)
+                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_VIEWS)
+                .setInteractionCount(99200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_PERCENTAGE)
+                .setReviewRating("83.9")
+                .setId(10)
+                .setTitle("Recommended Video 1")
+                .setDescription("You should watch this!")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setInternalProviderFlag2(0x0010010084108410L)
+                .build();
+
+        String[] partialProjection = {
+                WatchNextPrograms._ID,
+                WatchNextPrograms.COLUMN_TITLE,
+                WatchNextPrograms.COLUMN_SHORT_DESCRIPTION,
+                WatchNextPrograms.COLUMN_POSTER_ART_URI,
+                WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
+                WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+                WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI,
+                WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
+                WatchNextPrograms.COLUMN_DURATION_MILLIS,
+                WatchNextPrograms.COLUMN_INTENT_URI,
+                WatchNextPrograms.COLUMN_TRANSIENT,
+                WatchNextPrograms.COLUMN_TYPE,
+                WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+                WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
+                WatchNextPrograms.COLUMN_LOGO_URI,
+                WatchNextPrograms.COLUMN_AVAILABILITY,
+                WatchNextPrograms.COLUMN_STARTING_PRICE,
+                WatchNextPrograms.COLUMN_OFFER_PRICE,
+                WatchNextPrograms.COLUMN_RELEASE_DATE,
+                WatchNextPrograms.COLUMN_ITEM_COUNT,
+                WatchNextPrograms.COLUMN_LIVE,
+                WatchNextPrograms.COLUMN_INTERACTION_TYPE,
+                WatchNextPrograms.COLUMN_INTERACTION_COUNT,
+                WatchNextPrograms.COLUMN_AUTHOR,
+                WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE,
+                WatchNextPrograms.COLUMN_REVIEW_RATING,
+        };
+
+        ContentValues contentValues = previewProgram.toContentValues(true);
+        compareProgram(previewProgram,
+                WatchNextProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
+                true);
+
+        WatchNextProgram clonedFullyPopulatedProgram =
+                new WatchNextProgram.Builder(previewProgram).build();
+        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    private static WatchNextProgram createFullyPopulatedWatchNextProgram() {
+        return new WatchNextProgram.Builder()
+                .setTitle("Google")
+                .setInternalProviderId("ID-4321")
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(WatchNextPrograms.TYPE_MOVIE)
+                .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
+                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_2_3)
+                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(WatchNextPrograms.AVAILABILITY_AVAILABLE)
+                .setStartingPrice("12.99 USD")
+                .setOfferPrice("4.99 USD")
+                .setReleaseDate("1997")
+                .setItemCount(3)
+                .setLive(false)
+                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_LIKES)
+                .setInteractionCount(10200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_STARS)
+                .setReviewRating("4.5")
+                .setSearchable(false)
+                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+                .setAudioLanguages(new String [] {"eng", "kor"})
+                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
+                .setContentRatings(new TvContentRating[] {
+                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+                .setDescription("This is a sample program")
+                .setEpisodeNumber("Pilot", 0)
+                .setEpisodeTitle("Hello World")
+                .setLongDescription("This is a longer description than the previous description")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setSeasonNumber("The Final Season", 7)
+                .setSeasonTitle("The Final Season")
+                .setVideoHeight(1080)
+                .setVideoWidth(1920)
+                .setInternalProviderFlag1(0x4)
+                .setInternalProviderFlag2(0x3)
+                .setInternalProviderFlag3(0x2)
+                .setInternalProviderFlag4(0x1)
+                .setBrowsable(true)
+                .setContentId("CID-8442")
+                .build();
+    }
+
+    private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB,
+            boolean includeIdAndProtectedFields) {
+        assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
+        assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
+        assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
+        assertEquals(programA.getDescription(), programB.getDescription());
+        assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
+        assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
+        assertEquals(programA.getLongDescription(), programB.getLongDescription());
+        assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
+        assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
+        assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
+        assertEquals(programA.getTitle(), programB.getTitle());
+        assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
+        assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
+        assertEquals(programA.isSearchable(), programB.isSearchable());
+        assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
+        assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
+        assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
+        assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
+        assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
+        assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
+        assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
+        assertEquals(programA.getLastPlaybackPositionMillis(),
+                programB.getLastPlaybackPositionMillis());
+        assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
+        assertEquals(programA.getIntentUri(), programB.getIntentUri());
+        assertEquals(programA.isTransient(), programB.isTransient());
+        assertEquals(programA.getType(), programB.getType());
+        assertEquals(programA.getWatchNextType(), programB.getWatchNextType());
+        assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
+        assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
+        assertEquals(programA.getLogoUri(), programB.getLogoUri());
+        assertEquals(programA.getAvailability(), programB.getAvailability());
+        assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
+        assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
+        assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
+        assertEquals(programA.getItemCount(), programB.getItemCount());
+        assertEquals(programA.isLive(), programB.isLive());
+        assertEquals(programA.getInteractionType(), programB.getInteractionType());
+        assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
+        assertEquals(programA.getAuthor(), programB.getAuthor());
+        assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
+        assertEquals(programA.getReviewRating(), programB.getReviewRating());
+        assertEquals(programA.getContentId(), programB.getContentId());
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(programA.getId(), programB.getId());
+            // When we insert a channel using toContentValues() to the system, we drop some
+            // protected fields since they only can be modified by system apps.
+            assertEquals(programA.isBrowsable(), programB.isBrowsable());
+            assertEquals(programA.toContentValues(), programB.toContentValues());
+            assertEquals(programA, programB);
+        }
+    }
+
+    private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
+        MatrixCursor cursor = new MatrixCursor(projection);
+        MatrixCursor.RowBuilder builder = cursor.newRow();
+        for (String col : projection) {
+            if (col != null) {
+                builder.add(col, contentValues.get(col));
+            }
+        }
+        cursor.moveToFirst();
+        return cursor;
+    }
+}
diff --git a/tv-provider/tests/res/drawable/test_icon.png b/tv-provider/src/androidTest/res/drawable/test_icon.png
similarity index 100%
rename from tv-provider/tests/res/drawable/test_icon.png
rename to tv-provider/src/androidTest/res/drawable/test_icon.png
Binary files differ
diff --git a/tv-provider/AndroidManifest.xml b/tv-provider/src/main/AndroidManifest.xml
similarity index 100%
rename from tv-provider/AndroidManifest.xml
rename to tv-provider/src/main/AndroidManifest.xml
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..eeaa5ea 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
@@ -17,20 +17,18 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.content.Intent;
 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;
@@ -41,7 +39,6 @@
  *
  * @hide
  */
-@TargetApi(26)
 public abstract class BasePreviewProgram extends BaseProgram {
     /**
      * @hide
@@ -55,6 +52,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 +207,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 +217,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 +227,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 +245,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 +296,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/Channel.java b/tv-provider/src/main/java/android/support/media/tv/Channel.java
index 9b13e42..a24d948 100644
--- a/tv-provider/src/main/java/android/support/media/tv/Channel.java
+++ b/tv-provider/src/main/java/android/support/media/tv/Channel.java
@@ -17,7 +17,6 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.database.Cursor;
@@ -76,7 +75,6 @@
  *         TvContractCompat.buildChannelUri(existingChannel.getId()), null, null);
  * </pre>
  */
-@TargetApi(21)
 public final class Channel {
     /**
      * @hide
diff --git a/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java b/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
index 3df3a74..6d2fbaf 100644
--- a/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/PreviewProgram.java
@@ -17,7 +17,6 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.media.tv.TvContentRating;  // For javadoc gen of super class
@@ -74,7 +73,6 @@
  *         null, null);
  * </pre>
  */
-@TargetApi(26)
 public final class PreviewProgram extends BasePreviewProgram {
     /**
      * @hide
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..882916d 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
@@ -17,7 +17,6 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.media.tv.TvContentRating;  // For javadoc gen of super class
@@ -25,6 +24,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
@@ -71,7 +71,6 @@
  *         null, null);
  * </pre>
  */
-@TargetApi(21)
 public final class Program extends BaseProgram implements Comparable<Program> {
     /**
      * @hide
@@ -282,7 +281,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..61082aa 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
@@ -17,17 +17,19 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.ContentValues;
 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
@@ -76,7 +78,6 @@
  *         null, null);
  * </pre>
  */
-@TargetApi(26)
 public final class WatchNextProgram extends BasePreviewProgram {
     /**
      * @hide
@@ -87,16 +88,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/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java b/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java
deleted file mode 100644
index e222e40..0000000
--- a/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java
+++ /dev/null
@@ -1,383 +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.media.tv;
-
-import android.annotation.TargetApi;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.media.tv.TvContentRating;
-import android.net.Uri;
-import android.support.media.tv.TvContractCompat.Channels;
-import android.support.media.tv.TvContractCompat.PreviewPrograms;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Objects;
-
-/**
- * Tests that preview programs can be created using the Builder pattern and correctly obtain
- * values from them.
- */
-@SmallTest
-@SdkSuppress(minSdkVersion = 26)
-@TargetApi(26)
-public class PreviewProgramTest extends TestCase {
-
-    @Override
-    protected void tearDown() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        resolver.delete(Channels.CONTENT_URI, null, null);
-    }
-
-    @Test
-    public void testEmptyPreviewProgram() {
-        PreviewProgram emptyProgram = new PreviewProgram.Builder().build();
-        ContentValues contentValues = emptyProgram.toContentValues();
-        compareProgram(emptyProgram,
-                PreviewProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
-                true);
-    }
-
-    @Test
-    public void testSampleProgram() {
-        PreviewProgram sampleProgram = new PreviewProgram.Builder()
-                .setPackageName("My package")
-                .setTitle("Program Title")
-                .setDescription("This is a sample program")
-                .setEpisodeNumber(5)
-                .setSeasonNumber("The Final Season", 7)
-                .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png"))
-                .setChannelId(3)
-                .setWeight(70)
-                .build();
-        ContentValues contentValues = sampleProgram.toContentValues(true);
-        compareProgram(sampleProgram,
-                PreviewProgram.fromCursor(
-                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
-
-        PreviewProgram clonedSampleProgram = new PreviewProgram.Builder(sampleProgram).build();
-        compareProgram(sampleProgram, clonedSampleProgram, true);
-    }
-
-    @Test
-    public void testFullyPopulatedPreviewProgram() {
-        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(3);
-        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
-        compareProgram(fullyPopulatedProgram,
-                PreviewProgram.fromCursor(
-                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
-
-        PreviewProgram clonedFullyPopulatedProgram =
-                new PreviewProgram.Builder(fullyPopulatedProgram).build();
-        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
-    }
-
-    @Test
-    public void testPreviewProgramWithSystemContentProvider() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-        Channel channel = new Channel.Builder()
-                .setInputId("TestInputService")
-                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
-                .build();
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
-        assertNotNull(channelUri);
-
-        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(
-                ContentUris.parseId(channelUri));
-        Uri previewProgramUri = resolver.insert(PreviewPrograms.CONTENT_URI,
-                fullyPopulatedProgram.toContentValues());
-
-        PreviewProgram programFromSystemDb =
-                loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
-        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
-    }
-
-    @Test
-    public void testPreviewProgramUpdateWithContentProvider() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-        Channel channel = new Channel.Builder()
-                .setInputId("TestInputService")
-                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
-                .build();
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
-        assertNotNull(channelUri);
-
-        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(
-                ContentUris.parseId(channelUri));
-        Uri previewProgramUri = resolver.insert(PreviewPrograms.CONTENT_URI,
-                fullyPopulatedProgram.toContentValues());
-
-        PreviewProgram programFromSystemDb = loadPreviewProgramFromContentProvider(
-                resolver, previewProgramUri);
-        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
-
-        // Update a field from a fully loaded preview program.
-        PreviewProgram updatedProgram = new PreviewProgram.Builder(programFromSystemDb)
-                .setInteractionCount(programFromSystemDb.getInteractionCount() + 1).build();
-        assertEquals(1, resolver.update(
-                previewProgramUri, updatedProgram.toContentValues(), null, null));
-        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
-        compareProgram(updatedProgram, programFromSystemDb, false);
-
-        // Update a field with null from a fully loaded preview program.
-        updatedProgram = new PreviewProgram.Builder(updatedProgram)
-                .setLongDescription(null).build();
-        assertEquals(1, resolver.update(
-                previewProgramUri, updatedProgram.toContentValues(), null, null));
-        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
-        compareProgram(updatedProgram, programFromSystemDb, false);
-
-        // Update a field without referencing fully loaded preview program.
-        ContentValues values = new PreviewProgram.Builder().setInteractionCount(1).build()
-                .toContentValues();
-        assertEquals(1, values.size());
-        assertEquals(1, resolver.update(previewProgramUri, values, null, null));
-        programFromSystemDb = loadPreviewProgramFromContentProvider(resolver, previewProgramUri);
-        PreviewProgram expectedProgram = new PreviewProgram.Builder(programFromSystemDb)
-                .setInteractionCount(1).build();
-        compareProgram(expectedProgram, programFromSystemDb, false);
-    }
-
-    @Test
-    public void testPreviewProgramEquals() {
-        assertEquals(createFullyPopulatedPreviewProgram(1), createFullyPopulatedPreviewProgram(1));
-    }
-
-    private static PreviewProgram loadPreviewProgramFromContentProvider(
-            ContentResolver resolver, Uri previewProgramUri) {
-        try (Cursor cursor = resolver.query(previewProgramUri, null, null, null, null)) {
-            assertNotNull(cursor);
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            return PreviewProgram.fromCursor(cursor);
-        }
-    }
-
-    @Test
-    public void testPreviewProgramWithPartialData() {
-        PreviewProgram previewProgram = new PreviewProgram.Builder()
-                .setChannelId(3)
-                .setWeight(100)
-                .setInternalProviderId("ID-4321")
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(PreviewPrograms.TYPE_TV_EPISODE)
-                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_3_2)
-                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(PreviewPrograms.AVAILABILITY_FREE_WITH_SUBSCRIPTION)
-                .setStartingPrice("9.99 USD")
-                .setOfferPrice("3.99 USD")
-                .setReleaseDate(new Date(Date.UTC(97, 2, 8, 9, 30, 59)))
-                .setLive(false)
-                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_VIEWS)
-                .setInteractionCount(99200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_PERCENTAGE)
-                .setReviewRating("83.9")
-                .setId(10)
-                .setTitle("Recommended Video 1")
-                .setDescription("You should watch this!")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setInternalProviderFlag2(0x0010010084108410L)
-                .build();
-
-        String[] partialProjection = {
-                PreviewPrograms._ID,
-                PreviewPrograms.COLUMN_CHANNEL_ID,
-                PreviewPrograms.COLUMN_TITLE,
-                PreviewPrograms.COLUMN_SHORT_DESCRIPTION,
-                PreviewPrograms.COLUMN_POSTER_ART_URI,
-                PreviewPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
-                PreviewPrograms.COLUMN_INTERNAL_PROVIDER_ID,
-                PreviewPrograms.COLUMN_PREVIEW_VIDEO_URI,
-                PreviewPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
-                PreviewPrograms.COLUMN_DURATION_MILLIS,
-                PreviewPrograms.COLUMN_INTENT_URI,
-                PreviewPrograms.COLUMN_WEIGHT,
-                PreviewPrograms.COLUMN_TRANSIENT,
-                PreviewPrograms.COLUMN_TYPE,
-                PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
-                PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
-                PreviewPrograms.COLUMN_LOGO_URI,
-                PreviewPrograms.COLUMN_AVAILABILITY,
-                PreviewPrograms.COLUMN_STARTING_PRICE,
-                PreviewPrograms.COLUMN_OFFER_PRICE,
-                PreviewPrograms.COLUMN_RELEASE_DATE,
-                PreviewPrograms.COLUMN_ITEM_COUNT,
-                PreviewPrograms.COLUMN_LIVE,
-                PreviewPrograms.COLUMN_INTERACTION_TYPE,
-                PreviewPrograms.COLUMN_INTERACTION_COUNT,
-                PreviewPrograms.COLUMN_AUTHOR,
-                PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
-                PreviewPrograms.COLUMN_REVIEW_RATING,
-        };
-
-        ContentValues contentValues = previewProgram.toContentValues(true);
-        compareProgram(previewProgram,
-                PreviewProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
-                true);
-
-        PreviewProgram clonedFullyPopulatedProgram =
-                new PreviewProgram.Builder(previewProgram).build();
-        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
-    }
-
-    private static PreviewProgram createFullyPopulatedPreviewProgram(long channelId) {
-        return new PreviewProgram.Builder()
-                .setTitle("Google")
-                .setInternalProviderId("ID-4321")
-                .setChannelId(channelId)
-                .setWeight(100)
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(PreviewPrograms.TYPE_MOVIE)
-                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_2_3)
-                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(PreviewPrograms.AVAILABILITY_AVAILABLE)
-                .setStartingPrice("12.99 USD")
-                .setOfferPrice("4.99 USD")
-                .setReleaseDate("1997")
-                .setItemCount(3)
-                .setLive(false)
-                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_LIKES)
-                .setInteractionCount(10200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_STARS)
-                .setReviewRating("4.5")
-                .setSearchable(false)
-                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
-                .setAudioLanguages(new String [] {"eng", "kor"})
-                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
-                .setContentRatings(new TvContentRating[] {
-                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
-                .setDescription("This is a sample program")
-                .setEpisodeNumber("Pilot", 0)
-                .setEpisodeTitle("Hello World")
-                .setLongDescription("This is a longer description than the previous description")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setSeasonNumber("The Final Season", 7)
-                .setSeasonTitle("The Final Season")
-                .setVideoHeight(1080)
-                .setVideoWidth(1920)
-                .setInternalProviderFlag1(0x4)
-                .setInternalProviderFlag2(0x3)
-                .setInternalProviderFlag3(0x2)
-                .setInternalProviderFlag4(0x1)
-                .setBrowsable(true)
-                .setContentId("CID-8642")
-                .build();
-    }
-
-    private static void compareProgram(PreviewProgram programA, PreviewProgram programB,
-            boolean includeIdAndProtectedFields) {
-        assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
-        assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
-        assertEquals(programA.getChannelId(), programB.getChannelId());
-        assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
-        assertEquals(programA.getDescription(), programB.getDescription());
-        assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
-        assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
-        assertEquals(programA.getLongDescription(), programB.getLongDescription());
-        assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
-        assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
-        assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
-        assertEquals(programA.getTitle(), programB.getTitle());
-        assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
-        assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
-        assertEquals(programA.isSearchable(), programB.isSearchable());
-        assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
-        assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
-        assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
-        assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
-        assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
-        assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
-        assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
-        assertEquals(programA.getLastPlaybackPositionMillis(),
-                programB.getLastPlaybackPositionMillis());
-        assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
-        assertEquals(programA.getIntentUri(), programB.getIntentUri());
-        assertEquals(programA.getWeight(), programB.getWeight());
-        assertEquals(programA.isTransient(), programB.isTransient());
-        assertEquals(programA.getType(), programB.getType());
-        assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
-        assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
-        assertEquals(programA.getLogoUri(), programB.getLogoUri());
-        assertEquals(programA.getAvailability(), programB.getAvailability());
-        assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
-        assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
-        assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
-        assertEquals(programA.getItemCount(), programB.getItemCount());
-        assertEquals(programA.isLive(), programB.isLive());
-        assertEquals(programA.getInteractionType(), programB.getInteractionType());
-        assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
-        assertEquals(programA.getAuthor(), programB.getAuthor());
-        assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
-        assertEquals(programA.getReviewRating(), programB.getReviewRating());
-        assertEquals(programA.getContentId(), programB.getContentId());
-        if (includeIdAndProtectedFields) {
-            // Skip row ID since the one from system DB has the valid ID while the other does not.
-            assertEquals(programA.getId(), programB.getId());
-            assertEquals(programA.getPackageName(), programB.getPackageName());
-            // When we insert a channel using toContentValues() to the system, we drop some
-            // protected fields since they only can be modified by system apps.
-            assertEquals(programA.isBrowsable(), programB.isBrowsable());
-            assertEquals(programA.toContentValues(), programB.toContentValues());
-            assertEquals(programA, programB);
-        }
-    }
-
-    private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
-        MatrixCursor cursor = new MatrixCursor(projection);
-        MatrixCursor.RowBuilder builder = cursor.newRow();
-        for (String col : projection) {
-            if (col != null) {
-                builder.add(col, contentValues.get(col));
-            }
-        }
-        cursor.moveToFirst();
-        return cursor;
-    }
-}
diff --git a/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java b/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java
deleted file mode 100644
index b1a1041..0000000
--- a/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java
+++ /dev/null
@@ -1,361 +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.media.tv;
-
-import android.annotation.TargetApi;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.media.tv.TvContentRating;
-import android.net.Uri;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-
-import junit.framework.TestCase;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.Objects;
-
-/**
- * Tests that watch next programs can be created using the Builder pattern and correctly obtain
- * values from them.
- */
-@SmallTest
-@SdkSuppress(minSdkVersion = 26)
-@TargetApi(26)
-public class WatchNextProgramTest extends TestCase {
-
-    @Override
-    protected void tearDown() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        resolver.delete(WatchNextPrograms.CONTENT_URI, null, null);
-    }
-
-    @Test
-    public void testEmptyPreviewProgram() {
-        WatchNextProgram emptyProgram = new WatchNextProgram.Builder().build();
-        ContentValues contentValues = emptyProgram.toContentValues(true);
-        compareProgram(emptyProgram,
-                WatchNextProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
-                true);
-    }
-
-    @Test
-    public void testSampleProgram() {
-        WatchNextProgram sampleProgram = new WatchNextProgram.Builder()
-                .setTitle("Program Title")
-                .setDescription("This is a sample program")
-                .setEpisodeNumber(5)
-                .setSeasonNumber("The Final Season", 7)
-                .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png"))
-                .build();
-        ContentValues contentValues = sampleProgram.toContentValues(true);
-        compareProgram(sampleProgram,
-                WatchNextProgram.fromCursor(
-                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
-
-        WatchNextProgram clonedSampleProgram = new WatchNextProgram.Builder(sampleProgram).build();
-        compareProgram(sampleProgram, clonedSampleProgram, true);
-    }
-
-    @Test
-    public void testFullyPopulatedProgram() {
-        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
-        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
-        compareProgram(fullyPopulatedProgram,
-                WatchNextProgram.fromCursor(
-                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
-
-        WatchNextProgram clonedFullyPopulatedProgram =
-                new WatchNextProgram.Builder(fullyPopulatedProgram).build();
-        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
-    }
-
-    @Test
-    public void testChannelWithSystemContentProvider() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI,
-                fullyPopulatedProgram.toContentValues());
-
-        WatchNextProgram programFromSystemDb =
-                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
-        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
-    }
-
-    @Test
-    public void testWatchNextProgramUpdateWithContentProvider() {
-        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
-            return;
-        }
-
-        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
-        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
-        Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI,
-                fullyPopulatedProgram.toContentValues());
-
-        WatchNextProgram programFromSystemDb =
-                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
-        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
-
-        // Update a field from a fully loaded watch-next program.
-        WatchNextProgram updatedProgram = new WatchNextProgram.Builder(programFromSystemDb)
-                .setInteractionCount(programFromSystemDb.getInteractionCount() + 1).build();
-        assertEquals(1, resolver.update(
-                watchNextProgramUri, updatedProgram.toContentValues(), null, null));
-        programFromSystemDb =
-                loadWatchNextProgramFromContentProvider(resolver, watchNextProgramUri);
-        compareProgram(updatedProgram, programFromSystemDb, false);
-
-        // Update a field with null from a fully loaded watch-next program.
-        updatedProgram = new WatchNextProgram.Builder(updatedProgram)
-                .setPreviewVideoUri(null).build();
-        assertEquals(1, resolver.update(
-                watchNextProgramUri, updatedProgram.toContentValues(), null, null));
-        programFromSystemDb = loadWatchNextProgramFromContentProvider(
-                resolver, watchNextProgramUri);
-        compareProgram(updatedProgram, programFromSystemDb, false);
-
-        // Update a field without referencing fully watch-next program.
-        ContentValues values = new PreviewProgram.Builder().setInteractionCount(1).build()
-                .toContentValues();
-        assertEquals(1, values.size());
-        assertEquals(1, resolver.update(watchNextProgramUri, values, null, null));
-        programFromSystemDb = loadWatchNextProgramFromContentProvider(
-                resolver, watchNextProgramUri);
-        WatchNextProgram expectedProgram = new WatchNextProgram.Builder(programFromSystemDb)
-                .setInteractionCount(1).build();
-        compareProgram(expectedProgram, programFromSystemDb, false);
-    }
-
-    @Test
-    public void testWatchNextProgramEquals() {
-        assertEquals(createFullyPopulatedWatchNextProgram(),
-                createFullyPopulatedWatchNextProgram());
-    }
-
-    private static WatchNextProgram loadWatchNextProgramFromContentProvider(
-            ContentResolver resolver, Uri watchNextProgramUri) {
-        try (Cursor cursor = resolver.query(watchNextProgramUri, null, null, null, null)) {
-            assertNotNull(cursor);
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            return WatchNextProgram.fromCursor(cursor);
-        }
-    }
-
-    @Test
-    public void testWatchNextProgramWithPartialData() {
-        WatchNextProgram previewProgram = new WatchNextProgram.Builder()
-                .setInternalProviderId("ID-4321")
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(WatchNextPrograms.TYPE_TV_EPISODE)
-                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_3_2)
-                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(WatchNextPrograms.AVAILABILITY_FREE_WITH_SUBSCRIPTION)
-                .setStartingPrice("9.99 USD")
-                .setOfferPrice("3.99 USD")
-                .setReleaseDate(new Date(97, 2, 8))
-                .setLive(false)
-                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_VIEWS)
-                .setInteractionCount(99200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_PERCENTAGE)
-                .setReviewRating("83.9")
-                .setId(10)
-                .setTitle("Recommended Video 1")
-                .setDescription("You should watch this!")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setInternalProviderFlag2(0x0010010084108410L)
-                .build();
-
-        String[] partialProjection = {
-                WatchNextPrograms._ID,
-                WatchNextPrograms.COLUMN_TITLE,
-                WatchNextPrograms.COLUMN_SHORT_DESCRIPTION,
-                WatchNextPrograms.COLUMN_POSTER_ART_URI,
-                WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_FLAG2,
-                WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID,
-                WatchNextPrograms.COLUMN_PREVIEW_VIDEO_URI,
-                WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS,
-                WatchNextPrograms.COLUMN_DURATION_MILLIS,
-                WatchNextPrograms.COLUMN_INTENT_URI,
-                WatchNextPrograms.COLUMN_TRANSIENT,
-                WatchNextPrograms.COLUMN_TYPE,
-                WatchNextPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
-                WatchNextPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO,
-                WatchNextPrograms.COLUMN_LOGO_URI,
-                WatchNextPrograms.COLUMN_AVAILABILITY,
-                WatchNextPrograms.COLUMN_STARTING_PRICE,
-                WatchNextPrograms.COLUMN_OFFER_PRICE,
-                WatchNextPrograms.COLUMN_RELEASE_DATE,
-                WatchNextPrograms.COLUMN_ITEM_COUNT,
-                WatchNextPrograms.COLUMN_LIVE,
-                WatchNextPrograms.COLUMN_INTERACTION_TYPE,
-                WatchNextPrograms.COLUMN_INTERACTION_COUNT,
-                WatchNextPrograms.COLUMN_AUTHOR,
-                WatchNextPrograms.COLUMN_REVIEW_RATING_STYLE,
-                WatchNextPrograms.COLUMN_REVIEW_RATING,
-        };
-
-        ContentValues contentValues = previewProgram.toContentValues(true);
-        compareProgram(previewProgram,
-                WatchNextProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
-                true);
-
-        WatchNextProgram clonedFullyPopulatedProgram =
-                new WatchNextProgram.Builder(previewProgram).build();
-        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
-    }
-
-    private static WatchNextProgram createFullyPopulatedWatchNextProgram() {
-        return new WatchNextProgram.Builder()
-                .setTitle("Google")
-                .setInternalProviderId("ID-4321")
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(WatchNextPrograms.TYPE_MOVIE)
-                .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
-                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_2_3)
-                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(WatchNextPrograms.AVAILABILITY_AVAILABLE)
-                .setStartingPrice("12.99 USD")
-                .setOfferPrice("4.99 USD")
-                .setReleaseDate("1997")
-                .setItemCount(3)
-                .setLive(false)
-                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_LIKES)
-                .setInteractionCount(10200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_STARS)
-                .setReviewRating("4.5")
-                .setSearchable(false)
-                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
-                .setAudioLanguages(new String [] {"eng", "kor"})
-                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
-                .setContentRatings(new TvContentRating[] {
-                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
-                .setDescription("This is a sample program")
-                .setEpisodeNumber("Pilot", 0)
-                .setEpisodeTitle("Hello World")
-                .setLongDescription("This is a longer description than the previous description")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setSeasonNumber("The Final Season", 7)
-                .setSeasonTitle("The Final Season")
-                .setVideoHeight(1080)
-                .setVideoWidth(1920)
-                .setInternalProviderFlag1(0x4)
-                .setInternalProviderFlag2(0x3)
-                .setInternalProviderFlag3(0x2)
-                .setInternalProviderFlag4(0x1)
-                .setBrowsable(true)
-                .setContentId("CID-8442")
-                .build();
-    }
-
-    private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB,
-            boolean includeIdAndProtectedFields) {
-        assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
-        assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
-        assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
-        assertEquals(programA.getDescription(), programB.getDescription());
-        assertEquals(programA.getEpisodeNumber(), programB.getEpisodeNumber());
-        assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
-        assertEquals(programA.getLongDescription(), programB.getLongDescription());
-        assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
-        assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
-        assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
-        assertEquals(programA.getTitle(), programB.getTitle());
-        assertEquals(programA.getVideoHeight(), programB.getVideoHeight());
-        assertEquals(programA.getVideoWidth(), programB.getVideoWidth());
-        assertEquals(programA.isSearchable(), programB.isSearchable());
-        assertEquals(programA.getInternalProviderFlag1(), programB.getInternalProviderFlag1());
-        assertEquals(programA.getInternalProviderFlag2(), programB.getInternalProviderFlag2());
-        assertEquals(programA.getInternalProviderFlag3(), programB.getInternalProviderFlag3());
-        assertEquals(programA.getInternalProviderFlag4(), programB.getInternalProviderFlag4());
-        assertTrue(Objects.equals(programA.getSeasonTitle(), programB.getSeasonTitle()));
-        assertEquals(programA.getInternalProviderId(), programB.getInternalProviderId());
-        assertEquals(programA.getPreviewVideoUri(), programB.getPreviewVideoUri());
-        assertEquals(programA.getLastPlaybackPositionMillis(),
-                programB.getLastPlaybackPositionMillis());
-        assertEquals(programA.getDurationMillis(), programB.getDurationMillis());
-        assertEquals(programA.getIntentUri(), programB.getIntentUri());
-        assertEquals(programA.isTransient(), programB.isTransient());
-        assertEquals(programA.getType(), programB.getType());
-        assertEquals(programA.getWatchNextType(), programB.getWatchNextType());
-        assertEquals(programA.getPosterArtAspectRatio(), programB.getPosterArtAspectRatio());
-        assertEquals(programA.getThumbnailAspectRatio(), programB.getThumbnailAspectRatio());
-        assertEquals(programA.getLogoUri(), programB.getLogoUri());
-        assertEquals(programA.getAvailability(), programB.getAvailability());
-        assertEquals(programA.getStartingPrice(), programB.getStartingPrice());
-        assertEquals(programA.getOfferPrice(), programB.getOfferPrice());
-        assertEquals(programA.getReleaseDate(), programB.getReleaseDate());
-        assertEquals(programA.getItemCount(), programB.getItemCount());
-        assertEquals(programA.isLive(), programB.isLive());
-        assertEquals(programA.getInteractionType(), programB.getInteractionType());
-        assertEquals(programA.getInteractionCount(), programB.getInteractionCount());
-        assertEquals(programA.getAuthor(), programB.getAuthor());
-        assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
-        assertEquals(programA.getReviewRating(), programB.getReviewRating());
-        assertEquals(programA.getContentId(), programB.getContentId());
-        if (includeIdAndProtectedFields) {
-            // Skip row ID since the one from system DB has the valid ID while the other does not.
-            assertEquals(programA.getId(), programB.getId());
-            // When we insert a channel using toContentValues() to the system, we drop some
-            // protected fields since they only can be modified by system apps.
-            assertEquals(programA.isBrowsable(), programB.isBrowsable());
-            assertEquals(programA.toContentValues(), programB.toContentValues());
-            assertEquals(programA, programB);
-        }
-    }
-
-    private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
-        MatrixCursor cursor = new MatrixCursor(projection);
-        MatrixCursor.RowBuilder builder = cursor.newRow();
-        for (String col : projection) {
-            if (col != null) {
-                builder.add(col, contentValues.get(col));
-            }
-        }
-        cursor.moveToFirst();
-        return cursor;
-    }
-}
diff --git a/v13/Android.mk b/v13/Android.mk
index 1a95b75..d316032 100644
--- a/v13/Android.mk
+++ b/v13/Android.mk
@@ -33,9 +33,10 @@
 # android-support-v13, so we need to keep it static until they can be fixed.
 LOCAL_STATIC_ANDROID_LIBRARIES := \
         android-support-v4
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-        android-support-v4 \
-        android-support-annotations
+        android-support-v4
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 LOCAL_JAR_EXCLUDE_FILES := none
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 b645962..88e0bc0 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/Android.mk b/v14/preference/Android.mk
index 4885c3e..5228624 100644
--- a/v14/preference/Android.mk
+++ b/v14/preference/Android.mk
@@ -31,12 +31,13 @@
 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_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-v7-preference \
     android-support-v7-appcompat \
     android-support-v7-recyclerview \
-    android-support-v4 \
-    android-support-annotations
+    android-support-v4
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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/Android.mk b/v17/leanback/Android.mk
deleted file mode 100644
index d98abf6..0000000
--- a/v17/leanback/Android.mk
+++ /dev/null
@@ -1,47 +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)
-
-# Here is the final static library that apps can link against.
-# Applications that use this library must specify
-#
-#   LOCAL_STATIC_ANDROID_LIBRARIES := \
-#       android-support-v17-leanback \
-#       android-support-v7-recyclerview \
-#       android-support-v4
-#
-# in their makefiles to include the resources and their dependencies in their package.
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE := android-support-v17-leanback
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, common) \
-    $(call all-java-files-under, jbmr2) \
-    $(call all-java-files-under, kitkat) \
-    $(call all-java-files-under, api21) \
-    $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-v7-recyclerview \
-    android-support-compat \
-    android-support-core-ui \
-    android-support-media-compat \
-    android-support-fragment \
-    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/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 c6ce7ed..0000000
--- a/v17/leanback/build.gradle
+++ /dev/null
@@ -1,44 +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 {
-    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
-    minSdkVersion = 17
-}
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/Android.mk b/v17/preference-leanback/Android.mk
deleted file mode 100644
index 263d334..0000000
--- a/v17/preference-leanback/Android.mk
+++ /dev/null
@@ -1,49 +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)
-
-# Here is the final static library that apps can link against.
-# Applications that use this library must specify
-#
-#   LOCAL_STATIC_ANDROID_LIBRARIES := \
-#       android-support-v17-preference-leanback \
-#       android-support-v17-leanback \
-#       android-support-v14-preference \
-#       android-support-v7-preference \
-#       android-support-v7-appcompat \
-#       android-support-v7-recyclerview \
-#       android-support-v4
-#
-# in their makefiles to include the resources in their package.
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE := android-support-v17-preference-leanback
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under,api21) \
-    $(call all-java-files-under,src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-v17-leanback \
-    android-support-v14-preference \
-    android-support-v7-preference \
-    android-support-v7-appcompat \
-    android-support-v7-recyclerview \
-    android-support-v4 \
-    android-support-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/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..03ae26a 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -28,12 +28,13 @@
 # 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 := \
+    android-support-annotations
+LOCAL_STATIC_ANDROID_LIBRARIES := \
     android-support-compat \
     android-support-media-compat \
     android-support-core-utils \
     android-support-core-ui \
-    android-support-fragment \
-    android-support-annotations
+    android-support-fragment
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
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 16c2efa..106d0aa 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..6b9d05a 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
@@ -18,7 +18,6 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -26,6 +25,7 @@
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.R;
@@ -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
@@ -701,7 +702,7 @@
         return true;
     }
 
-    @TargetApi(23)
+    @RequiresApi(23)
     private StaticLayout createStaticLayoutForMeasuring(CharSequence text,
             Layout.Alignment alignment, int availableWidth, int maxLines) {
         // Can use the StaticLayout.Builder (along with TextView params added in or after
@@ -725,7 +726,6 @@
                 .build();
     }
 
-    @TargetApi(14)
     private StaticLayout createStaticLayoutForMeasuringPre23(CharSequence text,
             Layout.Alignment alignment, int availableWidth) {
         // Setup defaults.
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/TooltipCompat.java b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompat.java
index 470c3b2..4a583da 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompat.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipCompat.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.widget;
 
-import android.annotation.TargetApi;
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -28,34 +27,6 @@
  *
  */
 public class TooltipCompat  {
-    private interface ViewCompatImpl {
-        void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText);
-    }
-
-    private static class BaseViewCompatImpl implements ViewCompatImpl {
-        @Override
-        public void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
-            TooltipCompatHandler.setTooltipText(view, tooltipText);
-        }
-    }
-
-    @TargetApi(26)
-    private static class Api26ViewCompatImpl implements ViewCompatImpl {
-        @Override
-        public void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
-            view.setTooltipText(tooltipText);
-        }
-    }
-
-    private static final ViewCompatImpl IMPL;
-    static {
-        if (Build.VERSION.SDK_INT >= 26) {
-            IMPL = new Api26ViewCompatImpl();
-        } else {
-            IMPL = new BaseViewCompatImpl();
-        }
-    }
-
     /**
      * Sets the tooltip text for the view.
      * <p> Prior to API 26 this method sets or clears (when tooltip is null) the view's
@@ -66,7 +37,11 @@
      * @param tooltipText the tooltip text
      */
     public static void setTooltipText(@NonNull View view, @Nullable CharSequence tooltipText) {
-        IMPL.setTooltipText(view, tooltipText);
+        if (Build.VERSION.SDK_INT >= 26) {
+            view.setTooltipText(tooltipText);
+        } else {
+            TooltipCompatHandler.setTooltipText(view, tooltipText);
+        }
     }
 
     private TooltipCompat() {}
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/Android.mk b/v7/cardview/Android.mk
index 19d377c..2d9492d 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -27,7 +27,7 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under,src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SHARED_ANDROID_LIBRARIES := \
+LOCAL_JAVA_LIBRARIES := \
     android-support-annotations
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
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/Android.mk b/v7/gridlayout/Android.mk
index 5091081..81791da 100644
--- a/v7/gridlayout/Android.mk
+++ b/v7/gridlayout/Android.mk
@@ -29,10 +29,11 @@
 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_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
-    android-support-core-ui \
-    android-support-annotations
+    android-support-core-ui
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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 d387047..ae8bac0 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 79a4cc6..a3fd18e 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/Android.mk b/v7/palette/Android.mk
index f823d30..b7dafe9 100644
--- a/v7/palette/Android.mk
+++ b/v7/palette/Android.mk
@@ -29,11 +29,12 @@
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/src/main/res
-LOCAL_MANIFEST_FILE := AndroidManifest.xml
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
-    android-support-core-utils \
-    android-support-annotations
+    android-support-core-utils
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 35e9968..8829dcb 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)
 }
 
 supportLibrary {
@@ -20,5 +20,4 @@
     mavenGroup = LibraryGroups.SUPPORT
     inceptionYear = "2014"
     description = "Android Support Palette v7"
-    legacySourceLocation = true
 }
diff --git a/v7/palette/tests/AndroidManifest.xml b/v7/palette/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from v7/palette/tests/AndroidManifest.xml
rename to v7/palette/src/androidTest/AndroidManifest.xml
diff --git a/v7/palette/tests/NO_DOCS b/v7/palette/src/androidTest/NO_DOCS
similarity index 100%
rename from v7/palette/tests/NO_DOCS
rename to v7/palette/src/androidTest/NO_DOCS
diff --git a/v7/palette/tests/java/android/support/v7/graphics/BucketTests.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java
similarity index 100%
rename from v7/palette/tests/java/android/support/v7/graphics/BucketTests.java
rename to v7/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java
diff --git a/v7/palette/tests/java/android/support/v7/graphics/ConsistencyTest.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java
similarity index 100%
rename from v7/palette/tests/java/android/support/v7/graphics/ConsistencyTest.java
rename to v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java
diff --git a/v7/palette/tests/java/android/support/v7/graphics/MaxColorsTest.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java
similarity index 100%
rename from v7/palette/tests/java/android/support/v7/graphics/MaxColorsTest.java
rename to v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java
diff --git a/v7/palette/tests/java/android/support/v7/graphics/SwatchTests.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java
similarity index 100%
rename from v7/palette/tests/java/android/support/v7/graphics/SwatchTests.java
rename to v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java
diff --git a/v7/palette/tests/java/android/support/v7/graphics/TestUtils.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/TestUtils.java
similarity index 100%
rename from v7/palette/tests/java/android/support/v7/graphics/TestUtils.java
rename to v7/palette/src/androidTest/java/android/support/v7/graphics/TestUtils.java
diff --git a/v7/palette/tests/res/drawable-nodpi/photo.jpg b/v7/palette/src/androidTest/res/drawable-nodpi/photo.jpg
similarity index 100%
rename from v7/palette/tests/res/drawable-nodpi/photo.jpg
rename to v7/palette/src/androidTest/res/drawable-nodpi/photo.jpg
Binary files differ
diff --git a/v7/palette/AndroidManifest.xml b/v7/palette/src/main/AndroidManifest.xml
similarity index 100%
rename from v7/palette/AndroidManifest.xml
rename to v7/palette/src/main/AndroidManifest.xml
diff --git a/v7/preference/Android.mk b/v7/preference/Android.mk
index 1e3e0b6..5a3f57a 100644
--- a/v7/preference/Android.mk
+++ b/v7/preference/Android.mk
@@ -31,12 +31,14 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under,src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-v7-appcompat \
     android-support-v7-recyclerview \
-    android-support-v4 \
-    android-support-annotations
+    android-support-v4
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+LOCAL_EXPORT_PROGUARD_FLAG_FILES := proguard-rules.pro
 include $(BUILD_STATIC_JAVA_LIBRARY)
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 5e22560..2000ddd 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/Android.mk b/v7/recyclerview/Android.mk
index dcebe88..a62c3cd 100644
--- a/v7/recyclerview/Android.mk
+++ b/v7/recyclerview/Android.mk
@@ -29,11 +29,13 @@
 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_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
-    android-support-core-ui \
-    android-support-annotations
+    android-support-core-ui
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+LOCAL_EXPORT_PROGUARD_FLAG_FILES := proguard-rules.pro
 include $(BUILD_STATIC_JAVA_LIBRARY)
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..5064ace 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);
@@ -246,6 +258,7 @@
     method public abstract int getEnd();
     method public abstract int getEndAfterPadding();
     method public abstract int getEndPadding();
+    method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
     method public abstract int getMode();
     method public abstract int getModeInOther();
     method public abstract int getStartAfterPadding();
@@ -269,7 +282,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 +318,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 +341,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 +352,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 +431,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 64a1b07..a14ea97 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/LinearLayoutManager.java b/v7/recyclerview/src/main/java/android/support/v7/widget/LinearLayoutManager.java
index 27df490..fe4a37a 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/LinearLayoutManager.java
@@ -48,9 +48,9 @@
 
     static final boolean DEBUG = false;
 
-    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+    public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
 
-    public static final int VERTICAL = OrientationHelper.VERTICAL;
+    public static final int VERTICAL = RecyclerView.VERTICAL;
 
     public static final int INVALID_OFFSET = Integer.MIN_VALUE;
 
@@ -66,7 +66,7 @@
      * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
      */
     @RecyclerView.Orientation
-    int mOrientation;
+    int mOrientation = RecyclerView.DEFAULT_ORIENTATION;
 
     /**
      * Helper class that keeps temporary layout state.
@@ -78,8 +78,6 @@
     /**
      * Many calculations are made depending on orientation. To keep it clean, this interface
      * helps {@link LinearLayoutManager} make those decisions.
-     * Based on {@link #mOrientation}, an implementation is lazily created in
-     * {@link #ensureLayoutState} method.
      */
     OrientationHelper mOrientationHelper;
 
@@ -154,7 +152,7 @@
      * @param context Current context, will be used to access resources.
      */
     public LinearLayoutManager(Context context) {
-        this(context, VERTICAL, false);
+        this(context, RecyclerView.DEFAULT_ORIENTATION, false);
     }
 
     /**
@@ -335,13 +333,16 @@
         if (orientation != HORIZONTAL && orientation != VERTICAL) {
             throw new IllegalArgumentException("invalid orientation:" + orientation);
         }
+
         assertNotInLayoutOrScroll(null);
-        if (orientation == mOrientation) {
-            return;
+
+        if (orientation != mOrientation || mOrientationHelper == null) {
+            mOrientationHelper =
+                    OrientationHelper.createOrientationHelper(this, orientation);
+            mAnchorInfo.mOrientationHelper = mOrientationHelper;
+            mOrientation = orientation;
+            requestLayout();
         }
-        mOrientation = orientation;
-        mOrientationHelper = null;
-        requestLayout();
     }
 
     /**
@@ -516,7 +517,7 @@
             // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
             // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
             // child which can change between layout passes).
-            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused);
+            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
         }
         if (DEBUG) {
             Log.d(TAG, "Anchor info:" + mAnchorInfo);
@@ -781,7 +782,7 @@
         }
         final View focused = getFocusedChild();
         if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
-            anchorInfo.assignFromViewAndKeepVisibleRect(focused);
+            anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
             return true;
         }
         if (mLastStackFromEnd != mStackFromEnd) {
@@ -791,7 +792,7 @@
                 ? findReferenceChildClosestToEnd(recycler, state)
                 : findReferenceChildClosestToStart(recycler, state);
         if (referenceChild != null) {
-            anchorInfo.assignFromView(referenceChild);
+            anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
             // If all visible views are removed in 1 pass, reference child might be out of bounds.
             // If that is the case, offset it back to 0 so that we use these pre-layout children.
             if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
@@ -985,9 +986,6 @@
         if (mLayoutState == null) {
             mLayoutState = createLayoutState();
         }
-        if (mOrientationHelper == null) {
-            mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
-        }
     }
 
     /**
@@ -2370,7 +2368,8 @@
     /**
      * Simple data class to keep Anchor information
      */
-    class AnchorInfo {
+    static class AnchorInfo {
+        OrientationHelper mOrientationHelper;
         int mPosition;
         int mCoordinate;
         boolean mLayoutFromEnd;
@@ -2413,13 +2412,13 @@
                     && lp.getViewLayoutPosition() < state.getItemCount();
         }
 
-        public void assignFromViewAndKeepVisibleRect(View child) {
+        public void assignFromViewAndKeepVisibleRect(View child, int position) {
             final int spaceChange = mOrientationHelper.getTotalSpaceChange();
             if (spaceChange >= 0) {
-                assignFromView(child);
+                assignFromView(child, position);
                 return;
             }
-            mPosition = getPosition(child);
+            mPosition = position;
             if (mLayoutFromEnd) {
                 final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
                 final int childEnd = mOrientationHelper.getDecoratedEnd(child);
@@ -2460,7 +2459,7 @@
             }
         }
 
-        public void assignFromView(View child) {
+        public void assignFromView(View child, int position) {
             if (mLayoutFromEnd) {
                 mCoordinate = mOrientationHelper.getDecoratedEnd(child)
                         + mOrientationHelper.getTotalSpaceChange();
@@ -2468,7 +2467,7 @@
                 mCoordinate = mOrientationHelper.getDecoratedStart(child);
             }
 
-            mPosition = getPosition(child);
+            mPosition = position;
         }
     }
 
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/main/java/android/support/v7/widget/OrientationHelper.java
index 5e90f2e..99bcbaa 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/OrientationHelper.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/OrientationHelper.java
@@ -48,6 +48,14 @@
     }
 
     /**
+     * Returns the {@link android.support.v7.widget.RecyclerView.LayoutManager LayoutManager} that
+     * is associated with this OrientationHelper.
+     */
+    public RecyclerView.LayoutManager getLayoutManager() {
+        return mLayoutManager;
+    }
+
+    /**
      * Call this method after onLayout method is complete if state is NOT pre-layout.
      * This method records information like layout bounds that might be useful in the next layout
      * calculations.
@@ -435,4 +443,4 @@
             }
         };
     }
-}
\ No newline at end of file
+}
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..a287979 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;
@@ -216,6 +217,7 @@
     public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
     public static final int VERTICAL = LinearLayout.VERTICAL;
 
+    static final int DEFAULT_ORIENTATION = VERTICAL;
     public static final int NO_POSITION = -1;
     public static final long NO_ID = -1;
     public static final int INVALID_TYPE = -1;
@@ -348,7 +350,7 @@
                 return;
             }
             if (mLayoutFrozen) {
-                mLayoutRequestEaten = true;
+                mLayoutWasDefered = true;
                 return; //we'll process updates when ice age ends.
             }
             consumePendingUpdateOperations();
@@ -370,10 +372,21 @@
     boolean mEnableFastScroller;
     @VisibleForTesting boolean mFirstLayoutComplete;
 
-    // Counting lock to control whether we should ignore requestLayout calls from children or not.
-    private int mEatRequestLayout = 0;
+    /**
+     * The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of
+     * calls to {@link #startInterceptRequestLayout()} - number of calls to
+     * {@link #stopInterceptRequestLayout(boolean)} .  This is used to signal whether we
+     * should defer layout operations caused by layout requests from children of
+     * {@link RecyclerView}.
+     */
+    private int mInterceptRequestLayoutDepth = 0;
 
-    boolean mLayoutRequestEaten;
+    /**
+     * True if a call to requestLayout was intercepted and prevented from executing like normal and
+     * we plan on continuing with normal execution later.
+     */
+    boolean mLayoutWasDefered;
+
     boolean mLayoutFrozen;
     private boolean mIgnoreMotionEventTillDown;
 
@@ -385,8 +398,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 +407,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 +439,8 @@
      */
     private int mDispatchScrollCounter = 0;
 
+    @NonNull
+    private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory();
     private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
 
     ItemAnimator mItemAnimator = new DefaultItemAnimator();
@@ -1041,6 +1065,7 @@
         // bail out if layout is frozen
         setLayoutFrozen(false);
         setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+        processDataSetCompletelyChanged(true);
         requestLayout();
     }
     /**
@@ -1056,6 +1081,7 @@
         // bail out if layout is frozen
         setLayoutFrozen(false);
         setAdapterInternal(adapter, false, true);
+        processDataSetCompletelyChanged(false);
         requestLayout();
     }
 
@@ -1109,7 +1135,6 @@
         }
         mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
         mState.mStructureChanged = true;
-        setDataSetChangedAfterLayout();
     }
 
     /**
@@ -1342,7 +1367,7 @@
      * @return true if an animating view is removed
      */
     boolean removeAnimatingView(View view) {
-        eatRequestLayout();
+        startInterceptRequestLayout();
         final boolean removed = mChildHelper.removeViewIfHidden(view);
         if (removed) {
             final ViewHolder viewHolder = getChildViewHolderInt(view);
@@ -1353,7 +1378,7 @@
             }
         }
         // only clear request eaten flag if we removed the view.
-        resumeRequestLayout(!removed);
+        stopInterceptRequestLayout(!removed);
         return removed;
     }
 
@@ -1723,10 +1748,10 @@
                 .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                         | AdapterHelper.UpdateOp.MOVE)) {
             TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
-            eatRequestLayout();
+            startInterceptRequestLayout();
             onEnterLayoutOrScroll();
             mAdapterHelper.preProcess();
-            if (!mLayoutRequestEaten) {
+            if (!mLayoutWasDefered) {
                 if (hasUpdatedView()) {
                     dispatchLayout();
                 } else {
@@ -1734,7 +1759,7 @@
                     mAdapterHelper.consumePostponedUpdates();
                 }
             }
-            resumeRequestLayout(true);
+            stopInterceptRequestLayout(true);
             onExitLayoutOrScroll();
             TraceCompat.endSection();
         } else if (mAdapterHelper.hasPendingUpdates()) {
@@ -1778,7 +1803,7 @@
 
         consumePendingUpdateOperations();
         if (mAdapter != null) {
-            eatRequestLayout();
+            startInterceptRequestLayout();
             onEnterLayoutOrScroll();
             TraceCompat.beginSection(TRACE_SCROLL_TAG);
             fillRemainingScrollValues(mState);
@@ -1793,7 +1818,7 @@
             TraceCompat.endSection();
             repositionShadowingViews();
             onExitLayoutOrScroll();
-            resumeRequestLayout(false);
+            stopInterceptRequestLayout(false);
         }
         if (!mItemDecorations.isEmpty()) {
             invalidate();
@@ -1970,24 +1995,45 @@
         return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
     }
 
-
-    void eatRequestLayout() {
-        mEatRequestLayout++;
-        if (mEatRequestLayout == 1 && !mLayoutFrozen) {
-            mLayoutRequestEaten = false;
+    /**
+     * This method should be called before any code that may trigger a child view to cause a call to
+     * {@link RecyclerView#requestLayout()}.  Doing so enables {@link RecyclerView} to avoid
+     * reacting to additional redundant calls to {@link #requestLayout()}.
+     * <p>
+     * A call to this method must always be accompanied by a call to
+     * {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a
+     * child View to cause a call to {@link RecyclerView#requestLayout()}.
+     *
+     * @see #stopInterceptRequestLayout(boolean)
+     */
+    void startInterceptRequestLayout() {
+        mInterceptRequestLayoutDepth++;
+        if (mInterceptRequestLayoutDepth == 1 && !mLayoutFrozen) {
+            mLayoutWasDefered = false;
         }
     }
 
-    void resumeRequestLayout(boolean performLayoutChildren) {
-        if (mEatRequestLayout < 1) {
+    /**
+     * This method should be called after any code that may trigger a child view to cause a call to
+     * {@link RecyclerView#requestLayout()}.
+     * <p>
+     * A call to this method must always be accompanied by a call to
+     * {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child
+     * View to cause a call to {@link RecyclerView#requestLayout()}.
+     *
+     * @see #startInterceptRequestLayout()
+     */
+    void stopInterceptRequestLayout(boolean performLayoutChildren) {
+        if (mInterceptRequestLayoutDepth < 1) {
             //noinspection PointlessBooleanExpression
             if (DEBUG) {
-                throw new IllegalStateException("invalid eat request layout count"
+                throw new IllegalStateException("stopInterceptRequestLayout was called more "
+                        + "times than startInterceptRequestLayout."
                         + exceptionLabel());
             }
-            mEatRequestLayout = 1;
+            mInterceptRequestLayoutDepth = 1;
         }
-        if (!performLayoutChildren) {
+        if (!performLayoutChildren && !mLayoutFrozen) {
             // Reset the layout request eaten counter.
             // This is necessary since eatRequest calls can be nested in which case the other
             // call will override the inner one.
@@ -1996,19 +2042,19 @@
             //   eat layout for dispatchLayout
             //     a bunch of req layout calls arrive
 
-            mLayoutRequestEaten = false;
+            mLayoutWasDefered = false;
         }
-        if (mEatRequestLayout == 1) {
+        if (mInterceptRequestLayoutDepth == 1) {
             // when layout is frozen we should delay dispatchLayout()
-            if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen
+            if (performLayoutChildren && mLayoutWasDefered && !mLayoutFrozen
                     && mLayout != null && mAdapter != null) {
                 dispatchLayout();
             }
             if (!mLayoutFrozen) {
-                mLayoutRequestEaten = false;
+                mLayoutWasDefered = false;
             }
         }
-        mEatRequestLayout--;
+        mInterceptRequestLayoutDepth--;
     }
 
     /**
@@ -2038,10 +2084,10 @@
             assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
             if (!frozen) {
                 mLayoutFrozen = false;
-                if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
+                if (mLayoutWasDefered && mLayout != null && mAdapter != null) {
                     requestLayout();
                 }
-                mLayoutRequestEaten = false;
+                mLayoutWasDefered = false;
             } else {
                 final long now = SystemClock.uptimeMillis();
                 MotionEvent cancelEvent = MotionEvent.obtain(now, now,
@@ -2306,7 +2352,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 +2365,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 +2378,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 +2392,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 +2406,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.
@@ -2432,9 +2504,9 @@
                     // panic, focused view is not a child anymore, cannot call super.
                     return null;
                 }
-                eatRequestLayout();
+                startInterceptRequestLayout();
                 mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
-                resumeRequestLayout(false);
+                stopInterceptRequestLayout(false);
             }
             result = ff.findNextFocus(this, focused, direction);
         } else {
@@ -2446,9 +2518,9 @@
                     // panic, focused view is not a child anymore, cannot call super.
                     return null;
                 }
-                eatRequestLayout();
+                startInterceptRequestLayout();
                 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
-                resumeRequestLayout(false);
+                stopInterceptRequestLayout(false);
             }
         }
         if (result != null && !result.hasFocusable()) {
@@ -2480,9 +2552,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());
@@ -3155,7 +3235,7 @@
             }
             // custom onMeasure
             if (mAdapterUpdateDuringMeasure) {
-                eatRequestLayout();
+                startInterceptRequestLayout();
                 onEnterLayoutOrScroll();
                 processAdapterUpdatesAndSetAnimationFlags();
                 onExitLayoutOrScroll();
@@ -3168,7 +3248,7 @@
                     mState.mInPreLayout = false;
                 }
                 mAdapterUpdateDuringMeasure = false;
-                resumeRequestLayout(false);
+                stopInterceptRequestLayout(false);
             } else if (mState.mRunPredictiveAnimations) {
                 // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
                 // this means there is already an onMeasure() call performed to handle the pending
@@ -3184,15 +3264,16 @@
             } else {
                 mState.mItemCount = 0;
             }
-            eatRequestLayout();
+            startInterceptRequestLayout();
             mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
-            resumeRequestLayout(false);
+            stopInterceptRequestLayout(false);
             mState.mInPreLayout = false; // clear
         }
     }
 
     /**
-     * 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 +3450,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)
@@ -3618,7 +3701,7 @@
         mState.assertLayoutStep(State.STEP_START);
         fillRemainingScrollValues(mState);
         mState.mIsMeasuring = false;
-        eatRequestLayout();
+        startInterceptRequestLayout();
         mViewInfoStore.clear();
         onEnterLayoutOrScroll();
         processAdapterUpdatesAndSetAnimationFlags();
@@ -3698,7 +3781,7 @@
             clearOldPositions();
         }
         onExitLayoutOrScroll();
-        resumeRequestLayout(false);
+        stopInterceptRequestLayout(false);
         mState.mLayoutStep = State.STEP_LAYOUT;
     }
 
@@ -3707,7 +3790,7 @@
      * This step might be run multiple times if necessary (e.g. measure).
      */
     private void dispatchLayoutStep2() {
-        eatRequestLayout();
+        startInterceptRequestLayout();
         onEnterLayoutOrScroll();
         mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
         mAdapterHelper.consumeUpdatesInOnePass();
@@ -3725,7 +3808,7 @@
         mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
         mState.mLayoutStep = State.STEP_ANIMATIONS;
         onExitLayoutOrScroll();
-        resumeRequestLayout(false);
+        stopInterceptRequestLayout(false);
     }
 
     /**
@@ -3734,7 +3817,7 @@
      */
     private void dispatchLayoutStep3() {
         mState.assertLayoutStep(State.STEP_ANIMATIONS);
-        eatRequestLayout();
+        startInterceptRequestLayout();
         onEnterLayoutOrScroll();
         mState.mLayoutStep = State.STEP_START;
         if (mState.mRunSimpleAnimations) {
@@ -3792,6 +3875,7 @@
         mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
+        mDispatchItemsChangedEvent = false;
         mState.mRunSimpleAnimations = false;
 
         mState.mRunPredictiveAnimations = false;
@@ -3809,7 +3893,7 @@
 
         mLayout.onLayoutCompleted(mState);
         onExitLayoutOrScroll();
-        resumeRequestLayout(false);
+        stopInterceptRequestLayout(false);
         mViewInfoStore.clear();
         if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
             dispatchOnScrolled(0, 0);
@@ -3992,10 +4076,10 @@
 
     @Override
     public void requestLayout() {
-        if (mEatRequestLayout == 0 && !mLayoutFrozen) {
+        if (mInterceptRequestLayoutDepth == 0 && !mLayoutFrozen) {
             super.requestLayout();
         } else {
-            mLayoutRequestEaten = true;
+            mLayoutWasDefered = true;
         }
     }
 
@@ -4259,19 +4343,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();
     }
@@ -4852,7 +4938,7 @@
                 }
 
                 if (mAdapter != null) {
-                    eatRequestLayout();
+                    startInterceptRequestLayout();
                     onEnterLayoutOrScroll();
                     TraceCompat.beginSection(TRACE_SCROLL_TAG);
                     fillRemainingScrollValues(mState);
@@ -4868,7 +4954,7 @@
                     repositionShadowingViews();
 
                     onExitLayoutOrScroll();
-                    resumeRequestLayout(false);
+                    stopInterceptRequestLayout(false);
 
                     if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
                             && smoothScroller.isRunning()) {
@@ -5081,7 +5167,7 @@
             assertNotInLayoutOrScroll(null);
             mState.mStructureChanged = true;
 
-            setDataSetChangedAfterLayout();
+            processDataSetCompletelyChanged(true);
             if (!mAdapterHelper.hasPendingUpdates()) {
                 requestLayout();
             }
@@ -5130,6 +5216,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 +6577,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 +6600,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 +6631,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 +6642,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 +6657,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 +6754,7 @@
          *
          * @param holder The ViewHolder for the view being recycled
          */
-        public void onViewRecycled(VH holder) {
+        public void onViewRecycled(@NonNull VH holder) {
         }
 
         /**
@@ -6663,7 +6791,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 +6805,7 @@
          *
          * @param holder Holder of the view being attached
          */
-        public void onViewAttachedToWindow(VH holder) {
+        public void onViewAttachedToWindow(@NonNull VH holder) {
         }
 
         /**
@@ -6689,7 +6817,7 @@
          *
          * @param holder Holder of the view being detached
          */
-        public void onViewDetachedFromWindow(VH holder) {
+        public void onViewDetachedFromWindow(@NonNull VH holder) {
         }
 
         /**
@@ -6717,7 +6845,7 @@
          *
          * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
          */
-        public void registerAdapterDataObserver(AdapterDataObserver observer) {
+        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
             mObservable.registerObserver(observer);
         }
 
@@ -6731,7 +6859,7 @@
          *
          * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
          */
-        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+        public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
             mObservable.unregisterObserver(observer);
         }
 
@@ -6743,7 +6871,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 +6880,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 +6956,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 +7004,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 +7479,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 +7556,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 +9554,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 +9600,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
          */
@@ -10019,7 +10154,7 @@
             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
                     defStyleAttr, defStyleRes);
             properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation,
-                    VERTICAL);
+                    DEFAULT_ORIENTATION);
             properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1);
             properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false);
             properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false);
@@ -10734,8 +10869,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 +11233,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 +11710,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 +11865,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 +11945,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 +12104,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/StaggeredGridLayoutManager.java b/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
index f3ea045..55fb14e 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -59,9 +59,9 @@
 
     static final boolean DEBUG = false;
 
-    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+    public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
 
-    public static final int VERTICAL = OrientationHelper.VERTICAL;
+    public static final int VERTICAL = RecyclerView.VERTICAL;
 
     /**
      * Does not do anything to hide gaps.
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/Android.mk b/v7/recyclerview/tests/Android.mk
deleted file mode 100644
index c6299d7..0000000
--- a/v7/recyclerview/tests/Android.mk
+++ /dev/null
@@ -1,40 +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_STATIC_JAVA_LIBRARIES  := \
-        android-support-v7-recyclerview \
-        android-support-v4 \
-        android-support-annotations
-
-LOCAL_PACKAGE_NAME := RecyclerViewTests
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v7.recyclerview
-
-include $(BUILD_PACKAGE)
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..dd2c650 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,170 @@
                 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.KITKAT)
+    @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 layoutFrozenBug70402422() throws Throwable {
+        final Config config = new Config();
+        TestAdapter adapter = new TestAdapter(2);
+        adapter.setHasStableIds(false);
+        config.adapter(adapter);
+        setupByConfig(config, true);
+        final DummyItemAnimator itemAnimator = new DummyItemAnimator();
+        mRecyclerView.setItemAnimator(itemAnimator);
+
+        final View firstItemView = mRecyclerView
+                .findViewHolderForAdapterPosition(0).itemView;
+
+        itemAnimator.expect(DummyItemAnimator.REMOVE_START, 1);
+        mTestAdapter.deleteAndNotify(1, 1);
+        itemAnimator.waitFor(DummyItemAnimator.REMOVE_START);
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setLayoutFrozen(true);
+            }
+        });
+        // requestLayout during item animation, which should be eaten by setLayoutFrozen(true)
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                firstItemView.requestLayout();
+            }
+        });
+        assertTrue(firstItemView.isLayoutRequested());
+        assertFalse(mRecyclerView.isLayoutRequested());
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                itemAnimator.endAnimations();
+            }
+        });
+        // When setLayoutFrozen(false), the firstItemView should run a layout pass and clear
+        // isLayoutRequested() flag.
+        mLayoutManager.expectLayouts(1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setLayoutFrozen(false);
+            }
+        });
+        mLayoutManager.waitForLayout(1);
+        assertFalse(firstItemView.isLayoutRequested());
+        assertFalse(mRecyclerView.isLayoutRequested());
+    }
+
     @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/RecyclerViewAccessibilityLifecycleTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
index aed18bd..973b138 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.TargetApi;
 import android.os.Build;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
@@ -107,7 +106,6 @@
 
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Test
     public void processAllViewHolders() {
         RecyclerView rv = new RecyclerView(getActivity());
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..602fd56 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -46,6 +46,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -253,6 +254,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);
@@ -1352,7 +1533,33 @@
 
     @Test
     public void scrollToPositionCallback() throws Throwable {
-        RecyclerView recyclerView = new RecyclerView(getActivity());
+
+        class TestRecyclerView extends RecyclerView {
+
+            private CountDownLatch mDrawLatch;
+
+            TestRecyclerView(Context context) {
+                super(context);
+            }
+
+            public void expectDraws(int count) {
+                mDrawLatch = new CountDownLatch(count);
+            }
+
+            public void waitForDraw(int seconds) throws InterruptedException {
+                mDrawLatch.await(seconds, TimeUnit.SECONDS);
+            }
+
+            @Override
+            public void onDraw(Canvas c) {
+                super.onDraw(c);
+                if (mDrawLatch != null) {
+                    mDrawLatch.countDown();
+                }
+            }
+        }
+
+        TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
         TestLayoutManager tlm = new TestLayoutManager() {
             int scrollPos = RecyclerView.NO_POSITION;
 
@@ -1382,7 +1589,6 @@
             @Override
             public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                 rvCounter.incrementAndGet();
-                super.onScrolled(recyclerView, dx, dy);
             }
         });
 
@@ -1394,13 +1600,16 @@
                     }
                 });
 
+        recyclerView.expectDraws(1);
         tlm.expectLayouts(1);
         setRecyclerView(recyclerView);
         tlm.waitForLayout(2);
-
+        recyclerView.waitForDraw(2);
         assertEquals("RV on scroll should be called for initialization", 1, rvCounter.get());
         assertEquals("VTO on scroll should be called for initialization", 1,
                 viewGroupCounter.get());
+
+        recyclerView.expectDraws(1);
         tlm.expectLayouts(1);
         freezeLayout(true);
         scrollToPosition(3);
@@ -1408,13 +1617,15 @@
         freezeLayout(false);
         scrollToPosition(3);
         tlm.waitForLayout(2);
+        recyclerView.waitForDraw(2);
         assertEquals("RV on scroll should be called", 2, rvCounter.get());
         assertEquals("VTO on scroll should be called", 2, viewGroupCounter.get());
+
+        recyclerView.expectDraws(1);
         tlm.expectLayouts(1);
         requestLayoutOnUIThread(recyclerView);
         tlm.waitForLayout(2);
-        // wait for draw :/
-        Thread.sleep(1000);
+        recyclerView.waitForDraw(2);
         assertEquals("on scroll should NOT be called", 2, rvCounter.get());
         assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
     }
@@ -2263,7 +2474,7 @@
                 assertThat(hidden, CoreMatchers.notNullValue());
                 assertThat(updated, CoreMatchers.notNullValue());
 
-                mRecyclerView.eatRequestLayout();
+                mRecyclerView.startInterceptRequestLayout();
 
                 // first put the hidden child back
                 int index1 = mRecyclerView.indexOfChild(hidden.itemView);
@@ -4788,6 +4999,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 +5010,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/viewpager2/OWNERS b/viewpager2/OWNERS
new file mode 100644
index 0000000..e24ee8b
--- /dev/null
+++ b/viewpager2/OWNERS
@@ -0,0 +1 @@
+jgielzak@google.com
\ No newline at end of file
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
new file mode 100644
index 0000000..2fee643
--- /dev/null
+++ b/viewpager2/build.gradle
@@ -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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    api(project(":support-core-utils"))
+    api(project(":recyclerview-v7"))
+
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    name = "AndroidX Widget ViewPager2"
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SUPPORT
+    inceptionYear = "2017"
+    description = "AndroidX Widget ViewPager2"
+}
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/AndroidManifest.xml b/viewpager2/src/androidTest/AndroidManifest.xml
new file mode 100755
index 0000000..ac723e6
--- /dev/null
+++ b/viewpager2/src/androidTest/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.widget.viewpager2.test">
+    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+
+    <application android:supportsRtl="true">
+        <activity android:name="androidx.widget.viewpager2.tests.TestActivity"/>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.java b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.java
new file mode 100644
index 0000000..351ad9a
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/TestActivity.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.widget.viewpager2.tests;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.widget.viewpager2.test.R;
+
+public class TestActivity extends Activity {
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.activity_test_layout);
+    }
+}
diff --git a/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
new file mode 100644
index 0000000..7f5432b
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/widget/viewpager2/tests/ViewPager2Tests.java
@@ -0,0 +1,207 @@
+/*
+ * 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.widget.viewpager2.tests;
+
+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.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.IdlingRegistry;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.idling.CountingIdlingResource;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import androidx.widget.ViewPager2;
+import androidx.widget.viewpager2.test.R;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewPager2Tests {
+    private static final int[] sColors = {
+            Color.parseColor("#BBA9FF00"),
+            Color.parseColor("#BB00E87E"),
+            Color.parseColor("#BB00C7FF"),
+            Color.parseColor("#BBB30CE8"),
+            Color.parseColor("#BBFF00D0")};
+
+    @Rule
+    public final ActivityTestRule<TestActivity> mActivityTestRule;
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    private ViewPager2 mViewPager;
+    private CountingIdlingResource mIdlingResource;
+
+    public ViewPager2Tests() {
+        mActivityTestRule = new ActivityTestRule<>(TestActivity.class);
+    }
+
+    @Before
+    public void setUp() {
+        mViewPager = mActivityTestRule.getActivity().findViewById(R.id.view_pager);
+
+        mIdlingResource = new CountingIdlingResource(getClass().getSimpleName() + "IdlingResource");
+        mViewPager.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                if (newState == SCROLL_STATE_IDLE && !mIdlingResource.isIdleNow()) {
+                    mIdlingResource.decrement();
+                } else if (newState == SCROLL_STATE_SETTLING && mIdlingResource.isIdleNow()) {
+                    mIdlingResource.increment();
+                }
+            }
+        });
+        IdlingRegistry.getInstance().register(mIdlingResource);
+    }
+
+    @After
+    public void tearDown() {
+        IdlingRegistry.getInstance().unregister(mIdlingResource);
+    }
+
+    @Test
+    public void rendersAndHandlesSwiping() throws Throwable {
+        final int pageCount = sColors.length;
+
+        onView(withId(mViewPager.getId())).check(matches(isDisplayed()));
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mViewPager.setAdapter(
+                        new Adapter<ViewHolder>() {
+                            @NonNull
+                            @Override
+                            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+                                    int viewType) {
+                                return new ViewHolder(
+                                        mActivityTestRule.getActivity().getLayoutInflater().inflate(
+                                                R.layout.item_test_layout, parent, false)) {
+                                };
+                            }
+
+                            @Override
+                            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+                                TextView view = (TextView) holder.itemView;
+                                view.setText(String.valueOf(position));
+                                view.setBackgroundColor(sColors[position]);
+                            }
+
+                            @Override
+                            public int getItemCount() {
+                                return pageCount;
+                            }
+                        });
+            }
+        });
+
+        final int pageIxFirst = 0;
+        final int pageIxLast = pageCount - 1;
+        final int swipeCount = pageCount + 1; // two swipes beyond edge to test 'edge behavior'
+        int pageNumber = pageIxFirst;
+        for (int i = 0; i < swipeCount; i++, pageNumber = Math.min(pageIxLast, ++pageNumber)) {
+            verifyView(pageNumber);
+            performSwipe(ViewActions.swipeLeft());
+        }
+        assertThat(pageNumber, equalTo(pageIxLast));
+        for (int i = 0; i < swipeCount; i++, pageNumber = Math.max(pageIxFirst, --pageNumber)) {
+            verifyView(pageNumber);
+            performSwipe(ViewActions.swipeRight());
+        }
+        assertThat(pageNumber, equalTo(pageIxFirst));
+    }
+
+    private void verifyView(int pageNumber) {
+        onView(allOf(withId(R.id.text_view), isDisplayed())).check(
+                matches(withText(String.valueOf(pageNumber))));
+    }
+
+    private void performSwipe(ViewAction swipeAction) throws InterruptedException {
+        onView(allOf(isDisplayed(), withId(R.id.text_view))).perform(swipeAction);
+    }
+
+    @Test
+    public void itemViewSizeMatchParentEnforced() {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage(
+                "Item's root view must fill the whole ViewPager2 (use match_parent)");
+
+        ViewPager2 viewPager = new ViewPager2(InstrumentationRegistry.getContext());
+        viewPager.setAdapter(new Adapter<ViewHolder>() {
+            @NonNull
+            @Override
+            public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+                View view = new View(parent.getContext());
+                view.setLayoutParams(new ViewGroup.LayoutParams(50, 50)); // arbitrary fixed size
+                return new ViewHolder(view) {
+                };
+            }
+
+            @Override
+            public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+                // do nothing
+            }
+
+            @Override
+            public int getItemCount() {
+                return 1;
+            }
+        });
+
+        viewPager.measure(0, 0); // equivalent of unspecified
+    }
+
+    @Test
+    public void childrenNotAllowed() throws Exception {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("ViewPager2 does not support direct child views");
+
+        Context context = InstrumentationRegistry.getContext();
+        ViewPager2 viewPager = new ViewPager2(context);
+        viewPager.addView(new View(context));
+    }
+
+    // TODO: verify correct padding behavior
+    // TODO: add test for screen orientation change
+}
diff --git a/viewpager2/src/androidTest/res/layout/activity_test_layout.xml b/viewpager2/src/androidTest/res/layout/activity_test_layout.xml
new file mode 100644
index 0000000..3037029
--- /dev/null
+++ b/viewpager2/src/androidTest/res/layout/activity_test_layout.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.
+-->
+
+<androidx.widget.ViewPager2
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/view_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFF"
+    android:padding="16dp"/>
diff --git a/viewpager2/src/androidTest/res/layout/item_test_layout.xml b/viewpager2/src/androidTest/res/layout/item_test_layout.xml
new file mode 100644
index 0000000..dc6bdcf
--- /dev/null
+++ b/viewpager2/src/androidTest/res/layout/item_test_layout.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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/text_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center"/>
diff --git a/viewpager2/src/main/AndroidManifest.xml b/viewpager2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..198a6f7
--- /dev/null
+++ b/viewpager2/src/main/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.widget.viewpager2">
+    <uses-sdk android:minSdkVersion="14"/>
+</manifest>
\ No newline at end of file
diff --git a/viewpager2/src/main/java/androidx/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/widget/ViewPager2.java
new file mode 100644
index 0000000..9ebdea1
--- /dev/null
+++ b/viewpager2/src/main/java/androidx/widget/ViewPager2.java
@@ -0,0 +1,176 @@
+/*
+ * 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.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.PagerSnapHelper;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Work in progress: go/viewpager2
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class ViewPager2 extends ViewGroup {
+    // reused in layout(...)
+    private final Rect mTmpContainerRect = new Rect();
+    private final Rect mTmpChildRect = new Rect();
+
+    private RecyclerView mRecyclerView;
+
+    public ViewPager2(Context context) {
+        super(context);
+        initialize(context);
+    }
+
+    public ViewPager2(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(context);
+    }
+
+    @RequiresApi(21)
+    public ViewPager2(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        // TODO(b/70663531): handle attrs, defStyleAttr, defStyleRes
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initialize(context);
+    }
+
+    private void initialize(Context context) {
+        mRecyclerView = new RecyclerView(context);
+
+        LinearLayoutManager layoutManager = new LinearLayoutManager(context);
+        // TODO(b/69103581): add support for vertical layout
+        // TODO(b/69398856): add support for RTL
+        layoutManager.setOrientation(RecyclerView.HORIZONTAL);
+        mRecyclerView.setLayoutManager(layoutManager);
+
+        mRecyclerView.setLayoutParams(
+                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+        // TODO(b/70666992): add automated test for orientation change
+        new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
+
+        attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
+    }
+
+    /**
+     * TODO(b/70663708): decide on an Adapter class (for now reusing RecyclerView.Adapter)
+     *
+     * @see RecyclerView#setAdapter(Adapter)
+     */
+    public <VH extends ViewHolder> void setAdapter(final Adapter<VH> adapter) {
+        mRecyclerView.setAdapter(new Adapter<VH>() {
+            private final Adapter<VH> mAdapter = adapter;
+
+            @NonNull
+            @Override
+            public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+                VH viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
+
+                LayoutParams layoutParams = viewHolder.itemView.getLayoutParams();
+                if ((layoutParams.width | layoutParams.height) != LayoutParams.MATCH_PARENT) {
+                    // TODO(b/70666614): decide if throw an exception or wrap in FrameLayout
+                    // ourselves; consider accepting exact size equal to parent's exact size
+                    throw new IllegalStateException(String.format(
+                            "Item's root view must fill the whole %s (use match_parent)",
+                            ViewPager2.this.getClass().getSimpleName()));
+                }
+
+                return viewHolder;
+            }
+
+            @Override
+            public void onBindViewHolder(@NonNull VH holder, int position) {
+                mAdapter.onBindViewHolder(holder, position);
+            }
+
+            @Override
+            public int getItemCount() {
+                return mAdapter.getItemCount();
+            }
+        });
+    }
+
+    @Override
+    public void onViewAdded(View child) {
+        // TODO(b/70666620): consider adding a support for Decor views
+        throw new IllegalStateException(
+                getClass().getSimpleName() + " does not support direct child views");
+    }
+
+    /** @see RecyclerView#addOnScrollListener(RecyclerView.OnScrollListener) */
+    public void addOnScrollListener(RecyclerView.OnScrollListener listener) {
+        mRecyclerView.addOnScrollListener(listener);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // TODO(b/70666622): consider margin support
+        // TODO(b/70666626): consider delegating all this to RecyclerView
+        // TODO(b/70666625): write automated tests for this
+
+        measureChild(mRecyclerView, widthMeasureSpec, heightMeasureSpec);
+        int width = mRecyclerView.getMeasuredWidth();
+        int height = mRecyclerView.getMeasuredHeight();
+        int childState = mRecyclerView.getMeasuredState();
+
+        width += getPaddingLeft() + getPaddingRight();
+        height += getPaddingTop() + getPaddingBottom();
+
+        width = Math.max(width, getSuggestedMinimumWidth());
+        height = Math.max(height, getSuggestedMinimumHeight());
+
+        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+                resolveSizeAndState(height, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int width = mRecyclerView.getMeasuredWidth();
+        int height = mRecyclerView.getMeasuredHeight();
+
+        // TODO(b/70666626): consider delegating padding handling to the RecyclerView to avoid
+        // an unnatural page transition effect: http://shortn/_Vnug3yZpQT
+        mTmpContainerRect.left = getPaddingLeft();
+        mTmpContainerRect.right = r - l - getPaddingRight();
+        mTmpContainerRect.top = getPaddingTop();
+        mTmpContainerRect.bottom = b - t - getPaddingBottom();
+
+        Gravity.apply(Gravity.TOP | Gravity.START, width, height, mTmpContainerRect, mTmpChildRect);
+        mRecyclerView.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right,
+                mTmpChildRect.bottom);
+    }
+}
diff --git a/wear/Android.mk b/wear/Android.mk
index fe9b8d3..9e5bf5b 100644
--- a/wear/Android.mk
+++ b/wear/Android.mk
@@ -32,18 +32,22 @@
 
 include $(CLEAR_VARS)
 LOCAL_USE_AAPT2 := true
+LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE := android-support-wear
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-        android-support-core-ui \
-        android-support-annotations \
-        android-support-percent \
-        android-support-v7-recyclerview \
-        android-support-v4
+    android-support-core-ui \
+    android-support-percent \
+    android-support-v7-recyclerview \
+    android-support-v4 \
+    android-support-constraint-layout
 LOCAL_STATIC_JAVA_LIBRARIES := \
-        prebuilt-com.google.android.wearable-stubs
+    prebuilt-com.google.android.wearable-stubs
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
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..9c5a543 100644
--- a/wear/api/current.txt
+++ b/wear/api/current.txt
@@ -1,8 +1,8 @@
 package android.support.wear.ambient {
 
-  public final class AmbientMode extends android.app.Fragment {
+  public final deprecated 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,29 @@
 
   public final class AmbientMode.AmbientController {
     method public boolean isAmbient();
-    method public void setAutoResumeEnabled(boolean);
+  }
+
+  public final class AmbientModeSupport extends android.support.v4.app.Fragment {
+    ctor public AmbientModeSupport();
+    method public static <T extends android.support.v4.app.FragmentActivity> android.support.wear.ambient.AmbientModeSupport.AmbientController attach(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";
+  }
+
+  public static abstract class AmbientModeSupport.AmbientCallback {
+    ctor public AmbientModeSupport.AmbientCallback();
+    method public void onEnterAmbient(android.os.Bundle);
+    method public void onExitAmbient();
+    method public void onUpdateAmbient();
+  }
+
+  public static abstract interface AmbientModeSupport.AmbientCallbackProvider {
+    method public abstract android.support.wear.ambient.AmbientModeSupport.AmbientCallback getAmbientCallback();
+  }
+
+  public final class AmbientModeSupport.AmbientController {
+    method public boolean isAmbient();
   }
 
 }
@@ -49,7 +71,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 +204,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 0c08629..4878dab 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 {
@@ -41,6 +41,5 @@
     mavenGroup = LibraryGroups.SUPPORT
     inceptionYear = "2016"
     description = "Android Wear Support UI"
-    legacySourceLocation = true
     minSdkVersion = 23
 }
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/androidTest/AndroidManifest.xml b/wear/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..f7da293
--- /dev/null
+++ b/wear/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.wear.test">
+    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+    <application android:supportsRtl="true">
+        <activity android:name="android.support.wear.widget.LayoutTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.support.wear.widget.SwipeDismissFrameLayoutTestActivity"
+                  android:theme="@style/AppThemeNoSwipe">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.support.wear.widget.WearableRecyclerViewTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.support.wear.widget.drawer.DrawerTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.support.wear.ambient.AmbientModeTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <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>
+
+        <activity android:name="android.support.wear.ambient.AmbientModeSupportTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.support.wear.ambient.AmbientModeSupportResumeTestActivity">
+            <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"
+            android:value="true" />
+
+        <meta-data
+            android:name="com.google.android.wearable.watchface.preview"
+            android:resource="@drawable/preview_face" />
+
+        <meta-data
+            android:name="com.google.android.wearable.watchface.preview_circular"
+            android:resource="@drawable/preview_face_circular" />
+    </application>
+</manifest>
diff --git a/wear/tests/NO_DOCS b/wear/src/androidTest/NO_DOCS
similarity index 100%
rename from wear/tests/NO_DOCS
rename to wear/src/androidTest/NO_DOCS
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientDelegateTest.java b/wear/src/androidTest/java/android/support/wear/ambient/AmbientDelegateTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/ambient/AmbientDelegateTest.java
rename to wear/src/androidTest/java/android/support/wear/ambient/AmbientDelegateTest.java
diff --git a/wear/src/androidTest/java/android/support/wear/ambient/AmbientModeResumeTest.java b/wear/src/androidTest/java/android/support/wear/ambient/AmbientModeResumeTest.java
new file mode 100644
index 0000000..32de769
--- /dev/null
+++ b/wear/src/androidTest/java/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/src/androidTest/java/android/support/wear/ambient/AmbientModeResumeTestActivity.java b/wear/src/androidTest/java/android/support/wear/ambient/AmbientModeResumeTestActivity.java
new file mode 100644
index 0000000..0ca3c15
--- /dev/null
+++ b/wear/src/androidTest/java/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/android/support/wear/ambient/AmbientModeTest.java b/wear/src/androidTest/java/android/support/wear/ambient/AmbientModeTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/ambient/AmbientModeTest.java
rename to wear/src/androidTest/java/android/support/wear/ambient/AmbientModeTest.java
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeTestActivity.java b/wear/src/androidTest/java/android/support/wear/ambient/AmbientModeTestActivity.java
similarity index 100%
rename from wear/tests/src/android/support/wear/ambient/AmbientModeTestActivity.java
rename to wear/src/androidTest/java/android/support/wear/ambient/AmbientModeTestActivity.java
diff --git a/wear/src/androidTest/java/android/support/wear/utils/MetadataTestActivity.java b/wear/src/androidTest/java/android/support/wear/utils/MetadataTestActivity.java
new file mode 100644
index 0000000..b934b5b
--- /dev/null
+++ b/wear/src/androidTest/java/android/support/wear/utils/MetadataTestActivity.java
@@ -0,0 +1,37 @@
+/*
+ * 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.wear.utils;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.wear.test.R;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+public class MetadataTestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        assertTrue(MetadataConstants.isStandalone(this));
+        assertTrue(MetadataConstants.isNotificationBridgingEnabled(this));
+        assertEquals(R.drawable.preview_face,
+                MetadataConstants.getPreviewDrawableResourceId(this, false));
+        assertEquals(R.drawable.preview_face_circular,
+                MetadataConstants.getPreviewDrawableResourceId(this, true));
+    }
+}
diff --git a/wear/tests/src/android/support/wear/widget/BoxInsetLayoutTest.java b/wear/src/androidTest/java/android/support/wear/widget/BoxInsetLayoutTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/BoxInsetLayoutTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/BoxInsetLayoutTest.java
diff --git a/wear/tests/src/android/support/wear/widget/CircularProgressLayoutControllerTest.java b/wear/src/androidTest/java/android/support/wear/widget/CircularProgressLayoutControllerTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/CircularProgressLayoutControllerTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/CircularProgressLayoutControllerTest.java
diff --git a/wear/tests/src/android/support/wear/widget/CircularProgressLayoutTest.java b/wear/src/androidTest/java/android/support/wear/widget/CircularProgressLayoutTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/CircularProgressLayoutTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/CircularProgressLayoutTest.java
diff --git a/wear/tests/src/android/support/wear/widget/LayoutTestActivity.java b/wear/src/androidTest/java/android/support/wear/widget/LayoutTestActivity.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/LayoutTestActivity.java
rename to wear/src/androidTest/java/android/support/wear/widget/LayoutTestActivity.java
diff --git a/wear/src/androidTest/java/android/support/wear/widget/RoundedDrawableTest.java b/wear/src/androidTest/java/android/support/wear/widget/RoundedDrawableTest.java
new file mode 100644
index 0000000..b01b3fa
--- /dev/null
+++ b/wear/src/androidTest/java/android/support/wear/widget/RoundedDrawableTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wear.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.wear.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/** Tests for {@link RoundedDrawable} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RoundedDrawableTest {
+
+    @Rule
+    public final ActivityTestRule<LayoutTestActivity> mActivityRule = new ActivityTestRule<>(
+            LayoutTestActivity.class, true, false);
+    private static final int BITMAP_WIDTH = 64;
+    private static final int BITMAP_HEIGHT = 32;
+
+    private RoundedDrawable mRoundedDrawable;
+    private BitmapDrawable mBitmapDrawable;
+
+    @Mock
+    Canvas mMockCanvas;
+
+    @Before
+    public void setUp() {
+        mMockCanvas = mock(Canvas.class);
+        mActivityRule.launchActivity(new Intent().putExtra(LayoutTestActivity
+                        .EXTRA_LAYOUT_RESOURCE_ID,
+                android.support.wear.test.R.layout.rounded_drawable_layout));
+        mRoundedDrawable = new RoundedDrawable();
+        mBitmapDrawable =
+                new BitmapDrawable(
+                        mActivityRule.getActivity().getResources(),
+                        Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888));
+    }
+
+    @Test
+    public void colorFilterIsAppliedCorrectly() {
+        ColorFilter cf = new ColorFilter();
+        mRoundedDrawable.setColorFilter(cf);
+        assertEquals(cf, mRoundedDrawable.mPaint.getColorFilter());
+    }
+
+    @Test
+    public void alphaIsAppliedCorrectly() {
+        int alpha = 128;
+        mRoundedDrawable.setAlpha(alpha);
+        assertEquals(alpha, mRoundedDrawable.mPaint.getAlpha());
+    }
+
+    @Test
+    public void radiusIsAppliedCorrectly() {
+        int radius = 10;
+        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
+        mRoundedDrawable.setDrawable(mBitmapDrawable);
+        mRoundedDrawable.setClipEnabled(true);
+        mRoundedDrawable.setRadius(radius);
+        mRoundedDrawable.setBounds(bounds);
+        mRoundedDrawable.draw(mMockCanvas);
+        // One for background and one for the actual drawable, this should be called two times.
+        verify(mMockCanvas, times(2))
+                .drawRoundRect(
+                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
+                        eq((float) radius),
+                        eq((float) radius),
+                        any(Paint.class));
+    }
+
+    @Test
+    public void scalingIsAppliedCorrectly() {
+        int radius = 14;
+        // 14 px radius should apply 5 px padding due to formula ceil(radius * 1 - 1 / sqrt(2))
+        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
+        mRoundedDrawable.setDrawable(mBitmapDrawable);
+        mRoundedDrawable.setClipEnabled(false);
+        mRoundedDrawable.setRadius(radius);
+        mRoundedDrawable.setBounds(bounds);
+        mRoundedDrawable.draw(mMockCanvas);
+        assertEquals(BITMAP_WIDTH - 10, mBitmapDrawable.getBounds().width());
+        assertEquals(BITMAP_HEIGHT - 10, mBitmapDrawable.getBounds().height());
+        assertEquals(bounds.centerX(), mBitmapDrawable.getBounds().centerX());
+        assertEquals(bounds.centerY(), mBitmapDrawable.getBounds().centerY());
+        // Background should also be drawn
+        verify(mMockCanvas)
+                .drawRoundRect(
+                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
+                        eq((float) radius),
+                        eq((float) radius),
+                        any(Paint.class));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
+    public void inflate() {
+        RoundedDrawable roundedDrawable =
+                (RoundedDrawable) mActivityRule.getActivity().getDrawable(
+                        R.drawable.rounded_drawable);
+        assertEquals(
+                mActivityRule.getActivity().getColor(R.color.rounded_drawable_background_color),
+                roundedDrawable.getBackgroundColor());
+        assertTrue(roundedDrawable.isClipEnabled());
+        assertNotNull(roundedDrawable.getDrawable());
+        assertEquals(mActivityRule.getActivity().getResources().getDimensionPixelSize(
+                R.dimen.rounded_drawable_radius), roundedDrawable.getRadius());
+    }
+}
diff --git a/wear/tests/src/android/support/wear/widget/ScrollManagerTest.java b/wear/src/androidTest/java/android/support/wear/widget/ScrollManagerTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/ScrollManagerTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/ScrollManagerTest.java
diff --git a/wear/tests/src/android/support/wear/widget/SwipeDismissFrameLayoutTest.java b/wear/src/androidTest/java/android/support/wear/widget/SwipeDismissFrameLayoutTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/SwipeDismissFrameLayoutTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/SwipeDismissFrameLayoutTest.java
diff --git a/wear/tests/src/android/support/wear/widget/SwipeDismissFrameLayoutTestActivity.java b/wear/src/androidTest/java/android/support/wear/widget/SwipeDismissFrameLayoutTestActivity.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/SwipeDismissFrameLayoutTestActivity.java
rename to wear/src/androidTest/java/android/support/wear/widget/SwipeDismissFrameLayoutTestActivity.java
diff --git a/wear/tests/src/android/support/wear/widget/SwipeDismissPreferenceFragment.java b/wear/src/androidTest/java/android/support/wear/widget/SwipeDismissPreferenceFragment.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/SwipeDismissPreferenceFragment.java
rename to wear/src/androidTest/java/android/support/wear/widget/SwipeDismissPreferenceFragment.java
diff --git a/wear/tests/src/android/support/wear/widget/WearableLinearLayoutManagerTest.java b/wear/src/androidTest/java/android/support/wear/widget/WearableLinearLayoutManagerTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/WearableLinearLayoutManagerTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/WearableLinearLayoutManagerTest.java
diff --git a/wear/tests/src/android/support/wear/widget/WearableRecyclerViewTest.java b/wear/src/androidTest/java/android/support/wear/widget/WearableRecyclerViewTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/WearableRecyclerViewTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/WearableRecyclerViewTest.java
diff --git a/wear/tests/src/android/support/wear/widget/WearableRecyclerViewTestActivity.java b/wear/src/androidTest/java/android/support/wear/widget/WearableRecyclerViewTestActivity.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/WearableRecyclerViewTestActivity.java
rename to wear/src/androidTest/java/android/support/wear/widget/WearableRecyclerViewTestActivity.java
diff --git a/wear/src/androidTest/java/android/support/wear/widget/drawer/DrawerTestActivity.java b/wear/src/androidTest/java/android/support/wear/widget/drawer/DrawerTestActivity.java
new file mode 100644
index 0000000..414b97b
--- /dev/null
+++ b/wear/src/androidTest/java/android/support/wear/widget/drawer/DrawerTestActivity.java
@@ -0,0 +1,198 @@
+/*
+ * 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.wear.widget.drawer;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.IntDef;
+import android.support.wear.test.R;
+import android.support.wear.widget.drawer.WearableDrawerLayout.DrawerStateCallback;
+import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableNavigationDrawerAdapter;
+import android.util.ArrayMap;
+import android.view.Gravity;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+/**
+ * Test {@link Activity} for {@link WearableDrawerLayout} and implementations of {@link
+ * android.support.wear.widget.drawer.WearableDrawerView}.
+ */
+public class DrawerTestActivity extends Activity {
+
+    private static final int DRAWER_SIZE = 5;
+    private static final String STYLE_EXTRA = "style";
+    private static final String OPEN_TOP_IN_ONCREATE_EXTRA = "openTopInOnCreate";
+    private static final String OPEN_BOTTOM_IN_ONCREATE_EXTRA = "openBottomInOnCreate";
+    private static final String CLOSE_FIRST_DRAWER_OPENED = "closeFirstDrawerOpened";
+    private static final Map<Integer, Integer> STYLE_TO_RES_ID = new ArrayMap<>();
+
+    static {
+        STYLE_TO_RES_ID.put(
+                DrawerStyle.BOTH_DRAWER_NAV_MULTI_PAGE,
+                R.layout.test_multi_page_nav_drawer_layout);
+        STYLE_TO_RES_ID.put(
+                DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE,
+                R.layout.test_single_page_nav_drawer_layout);
+        STYLE_TO_RES_ID.put(
+                DrawerStyle.ONLY_ACTION_DRAWER_WITH_TITLE,
+                R.layout.test_only_action_drawer_with_title_layout);
+
+    }
+
+    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+    private final WearableNavigationDrawerAdapter mDrawerAdapter =
+            new WearableNavigationDrawerAdapter() {
+                @Override
+                public String getItemText(int pos) {
+                    return Integer.toString(pos);
+                }
+
+                @Override
+                public Drawable getItemDrawable(int pos) {
+                    return getDrawable(android.R.drawable.star_on);
+                }
+
+                @Override
+                public int getCount() {
+                    return DRAWER_SIZE;
+                }
+            };
+    private WearableActionDrawerView mActionDrawer;
+    private WearableDrawerLayout mDrawerLayout;
+    private WearableNavigationDrawerView mNavigationDrawer;
+    private final Runnable mCloseTopDrawerRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    mNavigationDrawer.getController().closeDrawer();
+                }
+            };
+    private final DrawerStateCallback mCloseFirstDrawerOpenedCallback =
+            new DrawerStateCallback() {
+                @Override
+                public void onDrawerOpened(WearableDrawerLayout layout,
+                        WearableDrawerView drawerView) {
+                    mMainThreadHandler.postDelayed(mCloseTopDrawerRunnable, 1000);
+                }
+            };
+    @DrawerStyle private int mNavigationStyle;
+    private boolean mOpenTopDrawerInOnCreate;
+    private boolean mOpenBottomDrawerInOnCreate;
+    private boolean mCloseFirstDrawerOpened;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        parseIntent(getIntent());
+
+        setContentView(STYLE_TO_RES_ID.get(mNavigationStyle));
+
+        mDrawerLayout = (WearableDrawerLayout) findViewById(R.id.drawer_layout);
+        mNavigationDrawer = (WearableNavigationDrawerView) findViewById(R.id.navigation_drawer);
+        mActionDrawer = (WearableActionDrawerView) findViewById(R.id.action_drawer);
+
+        if (mCloseFirstDrawerOpened) {
+            mDrawerLayout.setDrawerStateCallback(mCloseFirstDrawerOpenedCallback);
+        }
+
+        if (mNavigationDrawer != null) {
+            mNavigationDrawer.setAdapter(mDrawerAdapter);
+            if (mOpenTopDrawerInOnCreate) {
+                mDrawerLayout.openDrawer(Gravity.TOP);
+            } else {
+                mDrawerLayout.peekDrawer(Gravity.TOP);
+            }
+        }
+
+        if (mActionDrawer != null) {
+            if (mOpenBottomDrawerInOnCreate) {
+                mDrawerLayout.openDrawer(Gravity.BOTTOM);
+            } else {
+                mDrawerLayout.peekDrawer(Gravity.BOTTOM);
+            }
+        }
+    }
+
+    private void parseIntent(Intent intent) {
+        //noinspection WrongConstant - Linter doesn't know intent contains a NavigationStyle
+        mNavigationStyle = intent.getIntExtra(STYLE_EXTRA, DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE);
+        mOpenTopDrawerInOnCreate = intent.getBooleanExtra(OPEN_TOP_IN_ONCREATE_EXTRA, false);
+        mOpenBottomDrawerInOnCreate = intent.getBooleanExtra(OPEN_BOTTOM_IN_ONCREATE_EXTRA, false);
+        mCloseFirstDrawerOpened = intent.getBooleanExtra(CLOSE_FIRST_DRAWER_OPENED, false);
+    }
+
+    /**
+     * Which configuration of drawers should be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE,
+            DrawerStyle.BOTH_DRAWER_NAV_MULTI_PAGE,
+            DrawerStyle.ONLY_ACTION_DRAWER_WITH_TITLE
+    })
+    public @interface DrawerStyle {
+        int BOTH_DRAWER_NAV_SINGLE_PAGE = 0;
+        int BOTH_DRAWER_NAV_MULTI_PAGE = 1;
+        int ONLY_ACTION_DRAWER_WITH_TITLE = 2;
+    }
+
+    /**
+     * Builds an {@link Intent} to start this {@link Activity} with the appropriate extras.
+     */
+    public static class Builder {
+
+        @DrawerStyle private int mStyle = DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE;
+        private boolean mOpenTopDrawerInOnCreate = false;
+        private boolean mOpenBottomDrawerInOnCreate = false;
+        private boolean mCloseFirstDrawerOpened = false;
+
+        public Builder setStyle(@DrawerStyle int style) {
+            mStyle = style;
+            return this;
+        }
+
+        public Builder openTopDrawerInOnCreate() {
+            mOpenTopDrawerInOnCreate = true;
+            return this;
+        }
+
+        public Builder openBottomDrawerInOnCreate() {
+            mOpenBottomDrawerInOnCreate = true;
+            return this;
+        }
+
+        public Builder closeFirstDrawerOpened() {
+            mCloseFirstDrawerOpened = true;
+            return this;
+        }
+
+        public Intent build() {
+            return new Intent()
+                    .putExtra(STYLE_EXTRA, mStyle)
+                    .putExtra(OPEN_TOP_IN_ONCREATE_EXTRA, mOpenTopDrawerInOnCreate)
+                    .putExtra(OPEN_BOTTOM_IN_ONCREATE_EXTRA, mOpenBottomDrawerInOnCreate)
+                    .putExtra(CLOSE_FIRST_DRAWER_OPENED, mCloseFirstDrawerOpened);
+        }
+    }
+}
diff --git a/wear/tests/src/android/support/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java b/wear/src/androidTest/java/android/support/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/drawer/WearableDrawerLayoutEspressoTest.java
diff --git a/wear/tests/src/android/support/wear/widget/util/ArcSwipe.java b/wear/src/androidTest/java/android/support/wear/widget/util/ArcSwipe.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/util/ArcSwipe.java
rename to wear/src/androidTest/java/android/support/wear/widget/util/ArcSwipe.java
diff --git a/wear/tests/src/android/support/wear/widget/util/ArcSwipeTest.java b/wear/src/androidTest/java/android/support/wear/widget/util/ArcSwipeTest.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/util/ArcSwipeTest.java
rename to wear/src/androidTest/java/android/support/wear/widget/util/ArcSwipeTest.java
diff --git a/wear/tests/src/android/support/wear/widget/util/AsyncViewActions.java b/wear/src/androidTest/java/android/support/wear/widget/util/AsyncViewActions.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/util/AsyncViewActions.java
rename to wear/src/androidTest/java/android/support/wear/widget/util/AsyncViewActions.java
diff --git a/wear/tests/src/android/support/wear/widget/util/MoreViewAssertions.java b/wear/src/androidTest/java/android/support/wear/widget/util/MoreViewAssertions.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/util/MoreViewAssertions.java
rename to wear/src/androidTest/java/android/support/wear/widget/util/MoreViewAssertions.java
diff --git a/wear/tests/src/android/support/wear/widget/util/WakeLockRule.java b/wear/src/androidTest/java/android/support/wear/widget/util/WakeLockRule.java
similarity index 100%
rename from wear/tests/src/android/support/wear/widget/util/WakeLockRule.java
rename to wear/src/androidTest/java/android/support/wear/widget/util/WakeLockRule.java
diff --git a/wear/src/androidTest/java/com/google/android/wearable/compat/WearableActivityController.java b/wear/src/androidTest/java/com/google/android/wearable/compat/WearableActivityController.java
new file mode 100644
index 0000000..0a76af0
--- /dev/null
+++ b/wear/src/androidTest/java/com/google/android/wearable/compat/WearableActivityController.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 com.google.android.wearable.compat;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Mock version of {@link WearableActivityController}. During instrumentation testing, the tests
+ * would end up using this instead of the version implemented on device.
+ */
+public class WearableActivityController {
+
+    private static WearableActivityController sLastInstance;
+
+    public static WearableActivityController getLastInstance() {
+        return sLastInstance;
+    }
+
+    private AmbientCallback mCallback;
+    private boolean mAmbientEnabled = false;
+    private boolean mAutoResumeEnabled = true;
+    private boolean mAmbient = false;
+
+    public WearableActivityController(String tag, Activity activity, AmbientCallback callback) {
+        sLastInstance = this;
+        mCallback = callback;
+    }
+
+    // Methods required by the stub but not currently used in tests.
+    public void onCreate() {}
+    public void onResume() {}
+    public void onPause() {}
+    public void onStop() {}
+    public void onDestroy() {}
+
+    public void enterAmbient() {
+        mCallback.onEnterAmbient(null);
+    }
+
+    public void exitAmbient() {
+        mCallback.onExitAmbient();
+    }
+
+    public void updateAmbient() {
+        mCallback.onUpdateAmbient();
+    }
+
+    public void setAmbientEnabled() {
+        mAmbientEnabled = true;
+    }
+
+    public boolean isAmbientEnabled() {
+        return mAmbientEnabled;
+    }
+
+    public void setAutoResumeEnabled(boolean enabled) {
+        mAutoResumeEnabled = enabled;
+    }
+
+    public boolean isAutoResumeEnabled() {
+        return mAutoResumeEnabled;
+    }
+
+    public final boolean isAmbient() {
+        return mAmbient;
+    }
+
+    public void setAmbient(boolean ambient) {
+        mAmbient = ambient;
+    }
+
+    /** Stub version of {@link WearableActivityController.AmbientCallback}. */
+    public static class AmbientCallback {
+        public void onEnterAmbient(Bundle ambientDetails) {}
+
+        public void onExitAmbient() {}
+
+        public void onUpdateAmbient() {}
+    }
+}
diff --git a/wear/tests/res/drawable/preview_face.png b/wear/src/androidTest/res/drawable/preview_face.png
similarity index 100%
rename from wear/tests/res/drawable/preview_face.png
rename to wear/src/androidTest/res/drawable/preview_face.png
Binary files differ
diff --git a/wear/tests/res/drawable/preview_face_circular.png b/wear/src/androidTest/res/drawable/preview_face_circular.png
similarity index 100%
rename from wear/tests/res/drawable/preview_face_circular.png
rename to wear/src/androidTest/res/drawable/preview_face_circular.png
Binary files differ
diff --git a/wear/tests/res/drawable/rounded_drawable.xml b/wear/src/androidTest/res/drawable/rounded_drawable.xml
similarity index 100%
rename from wear/tests/res/drawable/rounded_drawable.xml
rename to wear/src/androidTest/res/drawable/rounded_drawable.xml
diff --git a/wear/tests/res/drawable/test_drawable.xml b/wear/src/androidTest/res/drawable/test_drawable.xml
similarity index 100%
rename from wear/tests/res/drawable/test_drawable.xml
rename to wear/src/androidTest/res/drawable/test_drawable.xml
diff --git a/wear/tests/res/layout/box_inset_layout_testcase_1.xml b/wear/src/androidTest/res/layout/box_inset_layout_testcase_1.xml
similarity index 100%
rename from wear/tests/res/layout/box_inset_layout_testcase_1.xml
rename to wear/src/androidTest/res/layout/box_inset_layout_testcase_1.xml
diff --git a/wear/tests/res/layout/box_inset_layout_testcase_2.xml b/wear/src/androidTest/res/layout/box_inset_layout_testcase_2.xml
similarity index 100%
rename from wear/tests/res/layout/box_inset_layout_testcase_2.xml
rename to wear/src/androidTest/res/layout/box_inset_layout_testcase_2.xml
diff --git a/wear/tests/res/layout/box_inset_layout_testcase_3.xml b/wear/src/androidTest/res/layout/box_inset_layout_testcase_3.xml
similarity index 100%
rename from wear/tests/res/layout/box_inset_layout_testcase_3.xml
rename to wear/src/androidTest/res/layout/box_inset_layout_testcase_3.xml
diff --git a/wear/tests/res/layout/box_inset_layout_testcase_4.xml b/wear/src/androidTest/res/layout/box_inset_layout_testcase_4.xml
similarity index 100%
rename from wear/tests/res/layout/box_inset_layout_testcase_4.xml
rename to wear/src/androidTest/res/layout/box_inset_layout_testcase_4.xml
diff --git a/wear/tests/res/layout/circular_progress_layout.xml b/wear/src/androidTest/res/layout/circular_progress_layout.xml
similarity index 100%
rename from wear/tests/res/layout/circular_progress_layout.xml
rename to wear/src/androidTest/res/layout/circular_progress_layout.xml
diff --git a/wear/tests/res/layout/curved_offsetting_helper_child.xml b/wear/src/androidTest/res/layout/curved_offsetting_helper_child.xml
similarity index 100%
rename from wear/tests/res/layout/curved_offsetting_helper_child.xml
rename to wear/src/androidTest/res/layout/curved_offsetting_helper_child.xml
diff --git a/wear/tests/res/layout/rounded_drawable_layout.xml b/wear/src/androidTest/res/layout/rounded_drawable_layout.xml
similarity index 100%
rename from wear/tests/res/layout/rounded_drawable_layout.xml
rename to wear/src/androidTest/res/layout/rounded_drawable_layout.xml
diff --git a/wear/tests/res/layout/swipe_dismiss_layout_testcase_1.xml b/wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_1.xml
similarity index 100%
rename from wear/tests/res/layout/swipe_dismiss_layout_testcase_1.xml
rename to wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_1.xml
diff --git a/wear/tests/res/layout/swipe_dismiss_layout_testcase_2.xml b/wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_2.xml
similarity index 100%
rename from wear/tests/res/layout/swipe_dismiss_layout_testcase_2.xml
rename to wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_2.xml
diff --git a/wear/tests/res/layout/swipe_dismiss_layout_testcase_3.xml b/wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_3.xml
similarity index 100%
rename from wear/tests/res/layout/swipe_dismiss_layout_testcase_3.xml
rename to wear/src/androidTest/res/layout/swipe_dismiss_layout_testcase_3.xml
diff --git a/wear/tests/res/layout/test_multi_page_nav_drawer_layout.xml b/wear/src/androidTest/res/layout/test_multi_page_nav_drawer_layout.xml
similarity index 100%
rename from wear/tests/res/layout/test_multi_page_nav_drawer_layout.xml
rename to wear/src/androidTest/res/layout/test_multi_page_nav_drawer_layout.xml
diff --git a/wear/tests/res/layout/test_only_action_drawer_with_title_layout.xml b/wear/src/androidTest/res/layout/test_only_action_drawer_with_title_layout.xml
similarity index 100%
rename from wear/tests/res/layout/test_only_action_drawer_with_title_layout.xml
rename to wear/src/androidTest/res/layout/test_only_action_drawer_with_title_layout.xml
diff --git a/wear/tests/res/layout/test_single_page_nav_drawer_layout.xml b/wear/src/androidTest/res/layout/test_single_page_nav_drawer_layout.xml
similarity index 100%
rename from wear/tests/res/layout/test_single_page_nav_drawer_layout.xml
rename to wear/src/androidTest/res/layout/test_single_page_nav_drawer_layout.xml
diff --git a/wear/tests/res/layout/wearable_recycler_view_basic.xml b/wear/src/androidTest/res/layout/wearable_recycler_view_basic.xml
similarity index 100%
rename from wear/tests/res/layout/wearable_recycler_view_basic.xml
rename to wear/src/androidTest/res/layout/wearable_recycler_view_basic.xml
diff --git a/wear/tests/res/menu/action_drawer.xml b/wear/src/androidTest/res/menu/action_drawer.xml
similarity index 100%
rename from wear/tests/res/menu/action_drawer.xml
rename to wear/src/androidTest/res/menu/action_drawer.xml
diff --git a/wear/tests/res/values-sw213dp/styles.xml b/wear/src/androidTest/res/values-sw213dp/styles.xml
similarity index 100%
rename from wear/tests/res/values-sw213dp/styles.xml
rename to wear/src/androidTest/res/values-sw213dp/styles.xml
diff --git a/wear/tests/res/values/colors.xml b/wear/src/androidTest/res/values/colors.xml
similarity index 100%
rename from wear/tests/res/values/colors.xml
rename to wear/src/androidTest/res/values/colors.xml
diff --git a/wear/tests/res/values/dimens.xml b/wear/src/androidTest/res/values/dimens.xml
similarity index 100%
rename from wear/tests/res/values/dimens.xml
rename to wear/src/androidTest/res/values/dimens.xml
diff --git a/wear/tests/res/values/styles.xml b/wear/src/androidTest/res/values/styles.xml
similarity index 100%
rename from wear/tests/res/values/styles.xml
rename to wear/src/androidTest/res/values/styles.xml
diff --git a/wear/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
similarity index 100%
rename from wear/AndroidManifest.xml
rename to wear/src/main/AndroidManifest.xml
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..d3a8a43 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>
@@ -46,8 +48,11 @@
  *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
  *     boolean isAmbient =  controller.isAmbient();
  * }</pre>
+ * @deprecated please use {@link AmbientModeSupport} instead.
  */
+@Deprecated
 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 +109,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 +119,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 +172,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 +181,9 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mDelegate.onCreate();
-        mDelegate.setAmbientEnabled();
+        if (mSuppliedCallback != null) {
+            mDelegate.setAmbientEnabled();
+        }
     }
 
     @Override
@@ -215,15 +222,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 +262,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/AmbientModeSupport.java b/wear/src/main/java/android/support/wear/ambient/AmbientModeSupport.java
new file mode 100644
index 0000000..97e26d9
--- /dev/null
+++ b/wear/src/main/java/android/support/wear/ambient/AmbientModeSupport.java
@@ -0,0 +1,281 @@
+/*
+ * 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.wear.ambient;
+
+import android.app.Activity;
+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.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.util.Log;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Use this as a headless Fragment to add ambient support to an Activity on Wearable devices.
+ * <p>
+ * The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK}
+ * permission to its manifest.
+ * <p>
+ * The primary entry  point for this code is the {@link #attach(FragmentActivity)} method.
+ * It should be called with an {@link FragmentActivity} as an argument and that
+ * {@link FragmentActivity} will then be able to receive ambient lifecycle events through
+ * an {@link AmbientCallback}. The {@link FragmentActivity} will also receive a
+ * {@link AmbientController} object from the attachment which can be used to query the current
+ * status of the ambient mode. An example of how to attach {@link AmbientModeSupport} to your
+ * {@link FragmentActivity} and use the {@link AmbientController} can be found below:
+ * <p>
+ * <pre class="prettyprint">{@code
+ *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
+ *     boolean isAmbient =  controller.isAmbient();
+ * }</pre>
+ */
+public final class AmbientModeSupport extends Fragment {
+    private static final String TAG = "AmbientMode";
+
+    /**
+     * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+     * whether burn-in protection is required. When this property is set to true, views must be
+     * shifted around periodically in ambient mode. To ensure that content isn't shifted off
+     * the screen, avoid placing content within 10 pixels of the edge of the screen. Activities
+     * should also avoid solid white areas to prevent pixel burn-in. Both of these requirements
+     * only apply in ambient mode, and only when this property is set to true.
+     */
+    public static final String EXTRA_BURN_IN_PROTECTION =
+            WearableActivityController.EXTRA_BURN_IN_PROTECTION;
+
+    /**
+     * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+     * whether the device has low-bit ambient mode. When this property is set to true, the screen
+     * supports fewer bits for each color in ambient mode. In this case, activities should disable
+     * anti-aliasing in ambient mode.
+     */
+    public static final String EXTRA_LOWBIT_AMBIENT =
+            WearableActivityController.EXTRA_LOWBIT_AMBIENT;
+
+    /**
+     * Fragment tag used by default when adding {@link AmbientModeSupport} to add ambient support to
+     * a {@link FragmentActivity}.
+     */
+    public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+
+    /**
+     * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
+     * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
+     * to bind the {@link AmbientModeSupport} to the instantiation of this interface.
+     * <p>
+     * <pre class="prettyprint">{@code
+     * return new AmbientMode.AmbientCallback() {
+     *     public void onEnterAmbient(Bundle ambientDetails) {...}
+     *     public void onExitAmbient(Bundle ambientDetails) {...}
+     *  }
+     * }</pre>
+     */
+    public interface AmbientCallbackProvider {
+        /**
+         * @return the {@link AmbientCallback} to be used by this class to communicate with the
+         * entity interested in ambient events.
+         */
+        AmbientCallback getAmbientCallback();
+    }
+
+    /**
+     * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
+     */
+    public abstract static class AmbientCallback {
+        /**
+         * Called when an activity is entering ambient mode. This event is sent while an activity is
+         * 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.
+         *
+         * @param ambientDetails bundle containing information about the display being used.
+         *                      It includes information about low-bit color and burn-in protection.
+         */
+        public void onEnterAmbient(Bundle ambientDetails) {}
+
+        /**
+         * Called when the system is updating the display for ambient mode. Activities may use this
+         * opportunity to update or invalidate views.
+         */
+        public void onUpdateAmbient() {}
+
+        /**
+         * Called when an activity should exit ambient mode. This event is sent while an activity is
+         * running (after onResume, before onPause).
+         */
+        public void onExitAmbient() {}
+    }
+
+    private final AmbientDelegate.AmbientCallback mCallback =
+            new AmbientDelegate.AmbientCallback() {
+                @Override
+                public void onEnterAmbient(Bundle ambientDetails) {
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onEnterAmbient(ambientDetails);
+                    }
+                }
+
+                @Override
+                public void onExitAmbient() {
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onExitAmbient();
+                    }
+                }
+
+                @Override
+                public void onUpdateAmbient() {
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onUpdateAmbient();
+                    }
+                }
+            };
+    private AmbientDelegate mDelegate;
+    @Nullable
+    private AmbientCallback mSuppliedCallback;
+    private AmbientController mController;
+
+    /**
+     * Constructor
+     */
+    public AmbientModeSupport() {
+        mController = new AmbientController();
+    }
+
+    @Override
+    @CallSuper
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback);
+
+        if (context instanceof AmbientCallbackProvider) {
+            mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
+        } else {
+            Log.w(TAG, "No callback provided - enabling only smart resume");
+        }
+    }
+
+    @Override
+    @CallSuper
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mDelegate.onCreate();
+        if (mSuppliedCallback != null) {
+            mDelegate.setAmbientEnabled();
+        }
+    }
+
+    @Override
+    @CallSuper
+    public void onResume() {
+        super.onResume();
+        mDelegate.onResume();
+    }
+
+    @Override
+    @CallSuper
+    public void onPause() {
+        mDelegate.onPause();
+        super.onPause();
+    }
+
+    @Override
+    @CallSuper
+    public void onStop() {
+        mDelegate.onStop();
+        super.onStop();
+    }
+
+    @Override
+    @CallSuper
+    public void onDestroy() {
+        mDelegate.onDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    @CallSuper
+    public void onDetach() {
+        mDelegate = null;
+        super.onDetach();
+    }
+
+    /**
+     * 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.
+     * @return the associated {@link AmbientController} which can be used to query the state of
+     * ambient mode.
+     */
+    public static <T extends FragmentActivity> AmbientController attach(T activity) {
+        FragmentManager fragmentManager = activity.getSupportFragmentManager();
+        AmbientModeSupport ambientFragment =
+                (AmbientModeSupport) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+        if (ambientFragment == null) {
+            AmbientModeSupport fragment = new AmbientModeSupport();
+            fragmentManager
+                    .beginTransaction()
+                    .add(fragment, FRAGMENT_TAG)
+                    .commit();
+            ambientFragment = fragment;
+        }
+        return ambientFragment.mController;
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mDelegate != null) {
+            mDelegate.dump(prefix, fd, writer, args);
+        }
+    }
+
+    @VisibleForTesting
+    void setAmbientDelegate(AmbientDelegate delegate) {
+        mDelegate = delegate;
+    }
+
+    /**
+     * 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. An instance of this class is returned to the user
+     * when they attach their {@link Activity} to {@link AmbientModeSupport}.
+     */
+    public final class AmbientController {
+        private static final String TAG = "AmbientController";
+
+        // Do not initialize outside of this class.
+        AmbientController() {}
+
+        /**
+         * @return {@code true} if the activity is currently in ambient.
+         */
+        public boolean isAmbient() {
+            return mDelegate == null ? false : mDelegate.isAmbient();
+        }
+    }
+}
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
deleted file mode 100644
index ce78477..0000000
--- a/wear/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.wear.test">
-    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
-
-    <uses-permission android:name="android.permission.WAKE_LOCK"/>
-
-    <application android:supportsRtl="true">
-        <activity android:name="android.support.wear.widget.LayoutTestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.support.wear.widget.SwipeDismissFrameLayoutTestActivity"
-                  android:theme="@style/AppThemeNoSwipe">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.support.wear.widget.WearableRecyclerViewTestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.support.wear.widget.drawer.DrawerTestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.support.wear.ambient.AmbientModeTestActivity">
-            <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"
-            android:value="true" />
-
-        <meta-data
-            android:name="com.google.android.wearable.watchface.preview"
-            android:resource="@drawable/preview_face" />
-
-        <meta-data
-            android:name="com.google.android.wearable.watchface.preview_circular"
-            android:resource="@drawable/preview_face_circular" />
-    </application>
-</manifest>
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeSupportResumeTest.java b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportResumeTest.java
new file mode 100644
index 0000000..1f50105
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportResumeTest.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 AmbientModeSupportResumeTest {
+    @Rule
+    public final WakeLockRule mWakeLock = new WakeLockRule();
+
+    @Rule
+    public final ActivityTestRule<AmbientModeSupportResumeTestActivity> mActivityRule =
+            new ActivityTestRule<>(AmbientModeSupportResumeTestActivity.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/AmbientModeSupportResumeTestActivity.java b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportResumeTestActivity.java
new file mode 100644
index 0000000..8fb1fe5
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportResumeTestActivity.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.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+public class AmbientModeSupportResumeTestActivity extends FragmentActivity {
+    AmbientModeSupport.AmbientController mAmbientController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAmbientController = AmbientModeSupport.attach(this);
+    }
+}
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTest.java b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTest.java
new file mode 100644
index 0000000..226d39b
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTest.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 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 AmbientModeSupportTest {
+    @Rule
+    public final WakeLockRule mWakeLock = new WakeLockRule();
+
+    @Rule
+    public final ActivityTestRule<AmbientModeSupportTestActivity> mActivityRule =
+            new ActivityTestRule<>(AmbientModeSupportTestActivity.class);
+
+    @Test
+    public void testEnterAmbientCallback() throws Throwable {
+        AmbientModeSupportTestActivity activity = mActivityRule.getActivity();
+
+        WearableActivityController.getLastInstance().enterAmbient();
+        assertTrue(activity.mEnterAmbientCalled);
+        assertFalse(activity.mUpdateAmbientCalled);
+        assertFalse(activity.mExitAmbientCalled);
+    }
+
+    @Test
+    public void testUpdateAmbientCallback() throws Throwable {
+        AmbientModeSupportTestActivity activity = mActivityRule.getActivity();
+
+        WearableActivityController.getLastInstance().updateAmbient();
+        assertFalse(activity.mEnterAmbientCalled);
+        assertTrue(activity.mUpdateAmbientCalled);
+        assertFalse(activity.mExitAmbientCalled);
+    }
+
+    @Test
+    public void testExitAmbientCallback() throws Throwable {
+        AmbientModeSupportTestActivity activity = mActivityRule.getActivity();
+
+        WearableActivityController.getLastInstance().exitAmbient();
+        assertFalse(activity.mEnterAmbientCalled);
+        assertFalse(activity.mUpdateAmbientCalled);
+        assertTrue(activity.mExitAmbientCalled);
+    }
+
+    @Test
+    public void testIsAmbientEnabled() {
+        assertTrue(WearableActivityController.getLastInstance().isAmbientEnabled());
+    }
+
+    @Test
+    public void testCallsControllerIsAmbient() {
+        AmbientModeSupportTestActivity activity = mActivityRule.getActivity();
+
+        WearableActivityController.getLastInstance().setAmbient(true);
+        assertTrue(activity.getAmbientController().isAmbient());
+
+        WearableActivityController.getLastInstance().setAmbient(false);
+        assertFalse(activity.getAmbientController().isAmbient());
+    }
+}
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTestActivity.java b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTestActivity.java
new file mode 100644
index 0000000..fc33edf
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeSupportTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.wear.ambient;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+public class AmbientModeSupportTestActivity extends FragmentActivity
+        implements AmbientModeSupport.AmbientCallbackProvider {
+    AmbientModeSupport.AmbientController mAmbientController;
+
+    boolean mEnterAmbientCalled;
+    boolean mUpdateAmbientCalled;
+    boolean mExitAmbientCalled;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAmbientController = AmbientModeSupport.attach(this);
+    }
+
+    @Override
+    public AmbientModeSupport.AmbientCallback getAmbientCallback() {
+        return new MyAmbientCallback();
+    }
+
+    private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
+
+        @Override
+        public void onEnterAmbient(Bundle ambientDetails) {
+            mEnterAmbientCalled = true;
+        }
+
+        @Override
+        public void onUpdateAmbient() {
+            mUpdateAmbientCalled = true;
+        }
+
+        @Override
+        public void onExitAmbient() {
+            mExitAmbientCalled = true;
+        }
+    }
+
+    public AmbientModeSupport.AmbientController getAmbientController() {
+        return mAmbientController;
+    }
+
+}
diff --git a/wear/tests/src/android/support/wear/utils/MetadataTestActivity.java b/wear/tests/src/android/support/wear/utils/MetadataTestActivity.java
deleted file mode 100644
index 86d92ab..0000000
--- a/wear/tests/src/android/support/wear/utils/MetadataTestActivity.java
+++ /dev/null
@@ -1,37 +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.wear.utils;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.wear.test.R;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-public class MetadataTestActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        assertTrue(MetadataConstants.isStandalone(this));
-        assertTrue(MetadataConstants.isNotificationBridgingEnabled(this));
-        assertEquals(R.drawable.preview_face,
-                MetadataConstants.getPreviewDrawableResourceId(this, false));
-        assertEquals(R.drawable.preview_face_circular,
-                MetadataConstants.getPreviewDrawableResourceId(this, true));
-    }
-}
\ No newline at end of file
diff --git a/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java b/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java
deleted file mode 100644
index 21aee7b..0000000
--- a/wear/tests/src/android/support/wear/widget/RoundedDrawableTest.java
+++ /dev/null
@@ -1,147 +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.wear.widget;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.Build;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.wear.test.R;
-import android.support.test.filters.SdkSuppress;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-/** Tests for {@link RoundedDrawable} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RoundedDrawableTest {
-
-    @Rule
-    public final ActivityTestRule<LayoutTestActivity> mActivityRule = new ActivityTestRule<>(
-            LayoutTestActivity.class, true, false);
-    private static final int BITMAP_WIDTH = 64;
-    private static final int BITMAP_HEIGHT = 32;
-
-    private RoundedDrawable mRoundedDrawable;
-    private BitmapDrawable mBitmapDrawable;
-
-    @Mock
-    Canvas mMockCanvas;
-
-    @Before
-    public void setUp() {
-        mMockCanvas = mock(Canvas.class);
-        mActivityRule.launchActivity(new Intent().putExtra(LayoutTestActivity
-                        .EXTRA_LAYOUT_RESOURCE_ID,
-                android.support.wear.test.R.layout.rounded_drawable_layout));
-        mRoundedDrawable = new RoundedDrawable();
-        mBitmapDrawable =
-                new BitmapDrawable(
-                        mActivityRule.getActivity().getResources(),
-                        Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888));
-    }
-
-    @Test
-    public void colorFilterIsAppliedCorrectly() {
-        ColorFilter cf = new ColorFilter();
-        mRoundedDrawable.setColorFilter(cf);
-        assertEquals(cf, mRoundedDrawable.mPaint.getColorFilter());
-    }
-
-    @Test
-    public void alphaIsAppliedCorrectly() {
-        int alpha = 128;
-        mRoundedDrawable.setAlpha(alpha);
-        assertEquals(alpha, mRoundedDrawable.mPaint.getAlpha());
-    }
-
-    @Test
-    public void radiusIsAppliedCorrectly() {
-        int radius = 10;
-        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
-        mRoundedDrawable.setDrawable(mBitmapDrawable);
-        mRoundedDrawable.setClipEnabled(true);
-        mRoundedDrawable.setRadius(radius);
-        mRoundedDrawable.setBounds(bounds);
-        mRoundedDrawable.draw(mMockCanvas);
-        // One for background and one for the actual drawable, this should be called two times.
-        verify(mMockCanvas, times(2))
-                .drawRoundRect(
-                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
-                        eq((float) radius),
-                        eq((float) radius),
-                        any(Paint.class));
-    }
-
-    @Test
-    public void scalingIsAppliedCorrectly() {
-        int radius = 14;
-        // 14 px radius should apply 5 px padding due to formula ceil(radius * 1 - 1 / sqrt(2))
-        Rect bounds = new Rect(0, 0, BITMAP_WIDTH, BITMAP_HEIGHT);
-        mRoundedDrawable.setDrawable(mBitmapDrawable);
-        mRoundedDrawable.setClipEnabled(false);
-        mRoundedDrawable.setRadius(radius);
-        mRoundedDrawable.setBounds(bounds);
-        mRoundedDrawable.draw(mMockCanvas);
-        assertEquals(BITMAP_WIDTH - 10, mBitmapDrawable.getBounds().width());
-        assertEquals(BITMAP_HEIGHT - 10, mBitmapDrawable.getBounds().height());
-        assertEquals(bounds.centerX(), mBitmapDrawable.getBounds().centerX());
-        assertEquals(bounds.centerY(), mBitmapDrawable.getBounds().centerY());
-        // Background should also be drawn
-        verify(mMockCanvas)
-                .drawRoundRect(
-                        eq(new RectF(0, 0, bounds.width(), bounds.height())),
-                        eq((float) radius),
-                        eq((float) radius),
-                        any(Paint.class));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
-    public void inflate() {
-        RoundedDrawable roundedDrawable =
-                (RoundedDrawable) mActivityRule.getActivity().getDrawable(
-                        R.drawable.rounded_drawable);
-        assertEquals(
-                mActivityRule.getActivity().getColor(R.color.rounded_drawable_background_color),
-                roundedDrawable.getBackgroundColor());
-        assertTrue(roundedDrawable.isClipEnabled());
-        assertNotNull(roundedDrawable.getDrawable());
-        assertEquals(mActivityRule.getActivity().getResources().getDimensionPixelSize(
-                R.dimen.rounded_drawable_radius), roundedDrawable.getRadius());
-    }
-}
diff --git a/wear/tests/src/android/support/wear/widget/drawer/DrawerTestActivity.java b/wear/tests/src/android/support/wear/widget/drawer/DrawerTestActivity.java
deleted file mode 100644
index 14af0e1..0000000
--- a/wear/tests/src/android/support/wear/widget/drawer/DrawerTestActivity.java
+++ /dev/null
@@ -1,198 +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.wear.widget.drawer;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.annotation.IntDef;
-import android.support.wear.test.R;
-import android.support.wear.widget.drawer.WearableDrawerLayout.DrawerStateCallback;
-import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableNavigationDrawerAdapter;
-import android.util.ArrayMap;
-import android.view.Gravity;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
-
-/**
- * Test {@link Activity} for {@link WearableDrawerLayout} and implementations of {@link
- * android.support.wear.widget.drawer.WearableDrawerView}.
- */
-public class DrawerTestActivity extends Activity {
-
-    private static final int DRAWER_SIZE = 5;
-    private static final String STYLE_EXTRA = "style";
-    private static final String OPEN_TOP_IN_ONCREATE_EXTRA = "openTopInOnCreate";
-    private static final String OPEN_BOTTOM_IN_ONCREATE_EXTRA = "openBottomInOnCreate";
-    private static final String CLOSE_FIRST_DRAWER_OPENED = "closeFirstDrawerOpened";
-    private static final Map<Integer, Integer> STYLE_TO_RES_ID = new ArrayMap<>();
-
-    static {
-        STYLE_TO_RES_ID.put(
-                DrawerStyle.BOTH_DRAWER_NAV_MULTI_PAGE,
-                R.layout.test_multi_page_nav_drawer_layout);
-        STYLE_TO_RES_ID.put(
-                DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE,
-                R.layout.test_single_page_nav_drawer_layout);
-        STYLE_TO_RES_ID.put(
-                DrawerStyle.ONLY_ACTION_DRAWER_WITH_TITLE,
-                R.layout.test_only_action_drawer_with_title_layout);
-
-    }
-
-    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
-    private final WearableNavigationDrawerAdapter mDrawerAdapter =
-            new WearableNavigationDrawerAdapter() {
-                @Override
-                public String getItemText(int pos) {
-                    return Integer.toString(pos);
-                }
-
-                @Override
-                public Drawable getItemDrawable(int pos) {
-                    return getDrawable(android.R.drawable.star_on);
-                }
-
-                @Override
-                public int getCount() {
-                    return DRAWER_SIZE;
-                }
-            };
-    private WearableActionDrawerView mActionDrawer;
-    private WearableDrawerLayout mDrawerLayout;
-    private WearableNavigationDrawerView mNavigationDrawer;
-    private final Runnable mCloseTopDrawerRunnable =
-            new Runnable() {
-                @Override
-                public void run() {
-                    mNavigationDrawer.getController().closeDrawer();
-                }
-            };
-    private final DrawerStateCallback mCloseFirstDrawerOpenedCallback =
-            new DrawerStateCallback() {
-                @Override
-                public void onDrawerOpened(WearableDrawerLayout layout,
-                        WearableDrawerView drawerView) {
-                    mMainThreadHandler.postDelayed(mCloseTopDrawerRunnable, 1000);
-                }
-            };
-    @DrawerStyle private int mNavigationStyle;
-    private boolean mOpenTopDrawerInOnCreate;
-    private boolean mOpenBottomDrawerInOnCreate;
-    private boolean mCloseFirstDrawerOpened;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        parseIntent(getIntent());
-
-        setContentView(STYLE_TO_RES_ID.get(mNavigationStyle));
-
-        mDrawerLayout = (WearableDrawerLayout) findViewById(R.id.drawer_layout);
-        mNavigationDrawer = (WearableNavigationDrawerView) findViewById(R.id.navigation_drawer);
-        mActionDrawer = (WearableActionDrawerView) findViewById(R.id.action_drawer);
-
-        if (mCloseFirstDrawerOpened) {
-            mDrawerLayout.setDrawerStateCallback(mCloseFirstDrawerOpenedCallback);
-        }
-
-        if (mNavigationDrawer != null) {
-            mNavigationDrawer.setAdapter(mDrawerAdapter);
-            if (mOpenTopDrawerInOnCreate) {
-                mDrawerLayout.openDrawer(Gravity.TOP);
-            } else {
-                mDrawerLayout.peekDrawer(Gravity.TOP);
-            }
-        }
-
-        if (mActionDrawer != null) {
-            if (mOpenBottomDrawerInOnCreate) {
-                mDrawerLayout.openDrawer(Gravity.BOTTOM);
-            } else {
-                mDrawerLayout.peekDrawer(Gravity.BOTTOM);
-            }
-        }
-    }
-
-    private void parseIntent(Intent intent) {
-        //noinspection WrongConstant - Linter doesn't know intent contains a NavigationStyle
-        mNavigationStyle = intent.getIntExtra(STYLE_EXTRA, DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE);
-        mOpenTopDrawerInOnCreate = intent.getBooleanExtra(OPEN_TOP_IN_ONCREATE_EXTRA, false);
-        mOpenBottomDrawerInOnCreate = intent.getBooleanExtra(OPEN_BOTTOM_IN_ONCREATE_EXTRA, false);
-        mCloseFirstDrawerOpened = intent.getBooleanExtra(CLOSE_FIRST_DRAWER_OPENED, false);
-    }
-
-    /**
-     * Which configuration of drawers should be used.
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-            DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE,
-            DrawerStyle.BOTH_DRAWER_NAV_MULTI_PAGE,
-            DrawerStyle.ONLY_ACTION_DRAWER_WITH_TITLE
-    })
-    public @interface DrawerStyle {
-    int BOTH_DRAWER_NAV_SINGLE_PAGE = 0;
-    int BOTH_DRAWER_NAV_MULTI_PAGE = 1;
-    int ONLY_ACTION_DRAWER_WITH_TITLE = 2;
-    }
-
-    /**
-     * Builds an {@link Intent} to start this {@link Activity} with the appropriate extras.
-     */
-    public static class Builder {
-
-        @DrawerStyle private int mStyle = DrawerStyle.BOTH_DRAWER_NAV_SINGLE_PAGE;
-        private boolean mOpenTopDrawerInOnCreate = false;
-        private boolean mOpenBottomDrawerInOnCreate = false;
-        private boolean mCloseFirstDrawerOpened = false;
-
-        public Builder setStyle(@DrawerStyle int style) {
-            mStyle = style;
-            return this;
-        }
-
-        public Builder openTopDrawerInOnCreate() {
-            mOpenTopDrawerInOnCreate = true;
-            return this;
-        }
-
-        public Builder openBottomDrawerInOnCreate() {
-            mOpenBottomDrawerInOnCreate = true;
-            return this;
-        }
-
-        public Builder closeFirstDrawerOpened() {
-            mCloseFirstDrawerOpened = true;
-            return this;
-        }
-
-        public Intent build() {
-            return new Intent()
-                    .putExtra(STYLE_EXTRA, mStyle)
-                    .putExtra(OPEN_TOP_IN_ONCREATE_EXTRA, mOpenTopDrawerInOnCreate)
-                    .putExtra(OPEN_BOTTOM_IN_ONCREATE_EXTRA, mOpenBottomDrawerInOnCreate)
-                    .putExtra(CLOSE_FIRST_DRAWER_OPENED, mCloseFirstDrawerOpened);
-        }
-    }
-}
diff --git a/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java b/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java
deleted file mode 100644
index 7823f23..0000000
--- a/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java
+++ /dev/null
@@ -1,95 +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 com.google.android.wearable.compat;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Mock version of {@link WearableActivityController}. During instrumentation testing, the tests
- * would end up using this instead of the version implemented on device.
- */
-public class WearableActivityController {
-
-    private static WearableActivityController sLastInstance;
-
-    public static WearableActivityController getLastInstance() {
-        return sLastInstance;
-    }
-
-    private AmbientCallback mCallback;
-    private boolean mAmbientEnabled = false;
-    private boolean mAutoResumeEnabled = false;
-    private boolean mAmbient = false;
-
-    public WearableActivityController(String tag, Activity activity, AmbientCallback callback) {
-        sLastInstance = this;
-        mCallback = callback;
-    }
-
-    // Methods required by the stub but not currently used in tests.
-    public void onCreate() {}
-    public void onResume() {}
-    public void onPause() {}
-    public void onStop() {}
-    public void onDestroy() {}
-
-    public void enterAmbient() {
-        mCallback.onEnterAmbient(null);
-    }
-
-    public void exitAmbient() {
-        mCallback.onExitAmbient();
-    }
-
-    public void updateAmbient() {
-        mCallback.onUpdateAmbient();
-    }
-
-    public void setAmbientEnabled() {
-        mAmbientEnabled = true;
-    }
-
-    public boolean isAmbientEnabled() {
-        return mAmbientEnabled;
-    }
-
-    public void setAutoResumeEnabled(boolean enabled) {
-        mAutoResumeEnabled = enabled;
-    }
-
-    public boolean isAutoResumeEnabled() {
-        return mAutoResumeEnabled;
-    }
-
-    public final boolean isAmbient() {
-        return mAmbient;
-    }
-
-    public void setAmbient(boolean ambient) {
-        mAmbient = ambient;
-    }
-
-    /** Stub version of {@link WearableActivityController.AmbientCallback}. */
-    public static class AmbientCallback {
-        public void onEnterAmbient(Bundle ambientDetails) {}
-
-        public void onExitAmbient() {}
-
-        public void onUpdateAmbient() {}
-    }
-}
diff --git a/webkit-codegen/.gitignore b/webkit-codegen/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webkit-codegen/.gitignore
diff --git a/webkit-codegen/OWNERS b/webkit-codegen/OWNERS
new file mode 100644
index 0000000..5d88928
--- /dev/null
+++ b/webkit-codegen/OWNERS
@@ -0,0 +1,5 @@
+boliu@google.com
+michaelbai@google.com
+tobiasjs@google.com
+torne@google.com
+gsennton@google.com
diff --git a/webkit-codegen/build.gradle b/webkit-codegen/build.gradle
new file mode 100644
index 0000000..92090e1
--- /dev/null
+++ b/webkit-codegen/build.gradle
@@ -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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+apply plugin: 'maven'
+apply plugin: 'application'
+
+mainClassName = "androidx.webkit.internal.codegen.Main"
+
+dependencies {
+    compile(JAVAPOET)
+    compile(LINT)
+
+    testCompile(JUNIT)
+}
diff --git a/webkit-codegen/src/main/java/androidx/webkit/internal/codegen/Main.java b/webkit-codegen/src/main/java/androidx/webkit/internal/codegen/Main.java
new file mode 100644
index 0000000..5a5f0f2
--- /dev/null
+++ b/webkit-codegen/src/main/java/androidx/webkit/internal/codegen/Main.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 androidx.webkit.internal.codegen;
+
+/**
+ * Main entry point class for the WebView Support Library code generation tool.
+ */
+public class Main {
+
+    /**
+     * Main entry point for the WebView Support Library code generation tool.
+     */
+    public static void main(String[] args) {
+    }
+}
diff --git a/webkit-codegen/src/test/resources/IGNORE_CHECKSTYLE b/webkit-codegen/src/test/resources/IGNORE_CHECKSTYLE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webkit-codegen/src/test/resources/IGNORE_CHECKSTYLE
diff --git a/samples/SupportLeanbackShowcase/app/.gitignore b/webkit/.gitignore
similarity index 100%
rename from samples/SupportLeanbackShowcase/app/.gitignore
rename to webkit/.gitignore
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/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