Merge "CardView improvements" into lmp-preview-dev
diff --git a/settings.gradle b/settings.gradle
index 2104257..b92434f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -18,3 +18,6 @@
 
 include ':support-v13'
 project(':support-v13').projectDir = new File(rootDir, 'v13')
+
+include ':support-leanback-v17'
+project(':support-leanback-v17').projectDir = new File(rootDir, 'v17/leanback')
diff --git a/v17/leanback/.classpath b/v17/leanback/.classpath
index a4763d1..7bc01d9 100644
--- a/v17/leanback/.classpath
+++ b/v17/leanback/.classpath
@@ -3,6 +3,7 @@
 	<classpathentry kind="src" path="src"/>
 	<classpathentry kind="src" path="gen"/>
 	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
 	<classpathentry kind="output" path="bin/classes"/>
 </classpath>
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index 6277930..f64ca8e 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -40,6 +40,16 @@
 
 # -----------------------------------------------------------------------
 
+#  A helper sub-library that makes direct use of API 21.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-api21
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api21)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 #  A helper sub-library that makes direct use of KitKat APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v17-leanback-kitkat
@@ -69,7 +79,7 @@
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
-        android-support-v17-leanback-common
+        android-support-v17-leanback-api21 android-support-v17-leanback-common
 LOCAL_JAVA_LIBRARIES := \
         android-support-v4 \
         android-support-v7-recyclerview \
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
new file mode 100644
index 0000000..92326d9
--- /dev/null
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -0,0 +1,48 @@
+/*
+ * 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.v17.leanback.R;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ViewGroup;
+
+class ShadowHelperApi21 {
+
+    static int sNormalZ = Integer.MIN_VALUE;
+    static int sFocusedZ;
+
+    private static void initializeResources(Resources res) {
+        if (sNormalZ == Integer.MIN_VALUE) {
+            sNormalZ = (int) res.getDimension(R.dimen.lb_material_shadow_normal_z);
+            sFocusedZ = (int) res.getDimension(R.dimen.lb_material_shadow_focused_z);
+        }
+    }
+
+    /* add shadows and return a implementation detail object */
+    public static Object addShadow(ViewGroup shadowContainer) {
+        initializeResources(shadowContainer.getResources());
+        shadowContainer.setBackground(new ColorDrawable(Color.TRANSPARENT));
+        shadowContainer.setZ(sNormalZ);
+        return shadowContainer;
+    }
+
+    /* set shadow focus level 0 for unfocused 1 for fully focused */
+    public static void setShadowFocusLevel(Object impl, float level) {
+        ViewGroup shadowContainer = (ViewGroup) impl;
+        shadowContainer.setZ(sNormalZ + level * (sFocusedZ - sNormalZ));
+    }
+}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 89d5f3b..0c8c413 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -8,21 +8,96 @@
 }
 
 android {
+    // WARNING: should be 17
     compileSdkVersion 'current'
+
     buildToolsVersion "19.0.1"
 
+    defaultConfig {
+        minSdkVersion 17
+        // TODO: get target from branch
+        //targetSdkVersion 19
+    }
+
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
-        main.java.srcDirs = [
-            'src',
-            'kitkat',
-            'jbmr2'
-        ]
-        main.res.srcDir 'res'
+        main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+        main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+        main.res.srcDirs = ['res']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/java'
     }
 
     lintOptions {
         // TODO: fix errors and reenable.
         abortOnError false
     }
-}
\ No newline at end of file
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support Leanback v17'
+                description "Android Support Leanback v17"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_search.png b/v17/leanback/res/drawable-hdpi/lb_action_search.png
deleted file mode 100644
index a70393b..0000000
--- a/v17/leanback/res/drawable-hdpi/lb_action_search.png
+++ /dev/null
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
index d58ac6c..542dd87 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
+++ b/v17/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/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
index 913e404..620aba6 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
+++ b/v17/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/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
index b097ef9..5db5420 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
+++ b/v17/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/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
index 16c1b81..78e9b1e 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_search.png b/v17/leanback/res/drawable-mdpi/lb_action_search.png
deleted file mode 100644
index dea3962..0000000
--- a/v17/leanback/res/drawable-mdpi/lb_action_search.png
+++ /dev/null
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
index 74f439d..ffdc8ec 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
+++ b/v17/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/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
index 98dc39a..5634efc 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
+++ b/v17/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/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
index 1b40609..7cc6d61 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
+++ b/v17/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/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
index 5fdd381..9e36b2e 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_action_search.png b/v17/leanback/res/drawable-xhdpi/lb_action_search.png
deleted file mode 100644
index 19658e4..0000000
--- a/v17/leanback/res/drawable-xhdpi/lb_action_search.png
+++ /dev/null
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
index ca78f0d..19ebbdf 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
index 1db0c04..e301816 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
+++ b/v17/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/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
index d4eaff1..c987c25 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
+++ b/v17/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/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
index 0558e8a..5904a4f 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_search.png b/v17/leanback/res/drawable-xxhdpi/lb_action_search.png
deleted file mode 100644
index a108638..0000000
--- a/v17/leanback/res/drawable-xxhdpi/lb_action_search.png
+++ /dev/null
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
index dae6e51..b9e31dc 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
+++ b/v17/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/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
index fac4c29..8e7e111 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
+++ b/v17/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/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
index 9ea80aa..c8a0790 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
+++ b/v17/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/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
index ff03bb9..cb97131 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/v17/leanback/res/layout/lb_browse_title.xml
index 4708375..75068d8 100644
--- a/v17/leanback/res/layout/lb_browse_title.xml
+++ b/v17/leanback/res/layout/lb_browse_title.xml
@@ -14,7 +14,7 @@
      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.support.v17.leanback.widget.TitleView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/browse_title_group"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -22,29 +22,5 @@
     android:paddingRight="?attr/browsePaddingRight"
     android:paddingLeft="?attr/browsePaddingLeft"
     android:paddingBottom="?attr/browsePaddingTop"
-    android:clipToPadding="false"
-    android:clipChildren="false">
+    style="?attr/browseTitleViewStyle" />
 
-    <ImageView
-        android:id="@+id/browse_badge"
-        android:layout_width="@dimen/lb_browse_title_icon_width"
-        android:layout_height="@dimen/lb_browse_title_icon_height"
-        android:layout_gravity="center_vertical|right"
-        android:src="@null"
-        android:visibility="gone"
-        style="?attr/browseTitleIconStyle"/>
-
-    <TextView
-        android:id="@+id/browse_title"
-        android:layout_width="@dimen/lb_browse_title_text_width"
-        android:layout_height="@dimen/lb_browse_title_height"
-        android:layout_gravity="center_vertical|right"
-        style="?attr/browseTitleTextStyle"/>
-
-    <android.support.v17.leanback.widget.SearchOrbView
-        android:id="@+id/browse_orb"
-        android:layout_height="wrap_content"
-        android:layout_width="wrap_content"
-        android:layout_gravity="center_vertical|left" />
-
-</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/v17/leanback/res/layout/lb_details_overview.xml
index beca042..f4e7443 100644
--- a/v17/leanback/res/layout/lb_details_overview.xml
+++ b/v17/leanback/res/layout/lb_details_overview.xml
@@ -21,40 +21,26 @@
     android:layout_height="wrap_content" >
 
     <!-- Background is applied to this inner layout -->
-    <RelativeLayout
+    <LinearLayout
         android:id="@+id/details_overview"
         android:layout_width="match_parent"
         android:layout_height="@dimen/lb_details_overview_height_large"
         android:layout_marginLeft="@dimen/lb_details_overview_margin_left"
         android:layout_marginRight="@dimen/lb_details_overview_margin_right"
         android:layout_marginBottom="@dimen/lb_details_overview_margin_bottom"
+        android:orientation="horizontal"
          >
 
         <ImageView
             android:id="@+id/details_overview_image"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true"
+            android:layout_height="match_parent"
             android:adjustViewBounds="true"
             />
 
-       <ImageView
-            android:id="@+id/details_overview_actions_more"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/lb_ic_actions_right_arrow"
-            android:layout_alignParentRight="true"
-            android:layout_alignParentBottom="true"
-            android:layout_marginRight="@dimen/lb_details_overview_actions_more_margin_right"
-            android:layout_marginBottom="@dimen/lb_details_overview_actions_more_margin_bottom"
-            />
-
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_toRightOf="@id/details_overview_image"
-            android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
-            android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
             android:layout_marginBottom="@dimen/lb_details_overview_description_margin_bottom"
             android:layout_marginTop="@dimen/lb_details_overview_description_margin_top"
             android:orientation="vertical" >
@@ -64,7 +50,10 @@
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             android:layout_weight="1"
-            android:gravity="top" />
+            android:gravity="top"
+            android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
+            android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
+            />
 
         <android.support.v17.leanback.widget.HorizontalGridView
             android:id="@+id/details_overview_actions"
@@ -75,10 +64,12 @@
             android:clipToPadding="false"
             android:focusable="true"
             android:focusableInTouchMode="true"
+            android:paddingLeft="@dimen/lb_details_overview_description_margin_left"
+            android:paddingRight="@dimen/lb_details_overview_description_margin_right"
             lb:horizontalMargin="@dimen/lb_details_overview_action_items_margin"
             lb:rowHeight="@dimen/lb_details_overview_actions_height" />
 
         </LinearLayout>
-    </RelativeLayout>
+    </LinearLayout>
 
 </FrameLayout>
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
index 0a14ff7..f7225e6 100644
--- a/v17/leanback/res/layout/lb_image_card_view.xml
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -70,6 +70,7 @@
                 android:layout_alignParentRight="true"
                 android:scaleType="fitCenter"
                 android:background="@color/lb_basic_card_info_bg_color"
+                android:visibility="gone"
                 android:contentDescription="@null" />
             <ImageView
                 android:id="@+id/fade_mask"
@@ -79,6 +80,7 @@
                 android:layout_alignParentBottom="true"
                 android:layout_toStartOf="@id/extra_badge"
                 android:scaleType="fitCenter"
+                android:visibility="gone"
                 android:contentDescription="@null" />
         </RelativeLayout>
     </FrameLayout>
diff --git a/v17/leanback/res/layout/lb_search_orb.xml b/v17/leanback/res/layout/lb_search_orb.xml
index f2b4dbb..0eff71e 100644
--- a/v17/leanback/res/layout/lb_search_orb.xml
+++ b/v17/leanback/res/layout/lb_search_orb.xml
@@ -28,7 +28,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:src="@drawable/lb_action_search"
+        android:src="@drawable/lb_ic_in_app_search"
         android:contentDescription="@string/orb_search_action" />
 
 </merge>
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/v17/leanback/res/layout/lb_title_view.xml
new file mode 100644
index 0000000..590a683
--- /dev/null
+++ b/v17/leanback/res/layout/lb_title_view.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <ImageView
+        android:id="@+id/browse_badge"
+        android:layout_width="@dimen/lb_browse_title_text_width"
+        android:layout_height="@dimen/lb_browse_title_height"
+        android:layout_gravity="center_vertical|right"
+        android:src="@null"
+        android:visibility="gone"
+        style="?attr/browseTitleIconStyle"/>
+
+    <TextView
+        android:id="@+id/browse_title"
+        android:layout_width="@dimen/lb_browse_title_text_width"
+        android:layout_height="@dimen/lb_browse_title_height"
+        android:layout_gravity="center_vertical|right"
+        style="?attr/browseTitleTextStyle"/>
+
+    <android.support.v17.leanback.widget.SearchOrbView
+        android:id="@+id/browse_orb"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical|left"
+        android:visibility="invisible" />
+
+</merge>
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
index 0720501..4cd3a22 100644
--- a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -14,17 +14,24 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<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:orientation="vertical">
+    android:layout_height="match_parent" >
 
-    <include layout="@layout/lb_browse_title" />
-
-    <FrameLayout
-        android:id="@+id/browse_grid_dock"
+    <android.support.v17.leanback.app.BrowseFrameLayout
+        android:id="@+id/browse_frame"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent" >
 
-</FrameLayout>
\ No newline at end of file
+        <include layout="@layout/lb_browse_title" />
+
+        <FrameLayout
+            android:id="@+id/browse_grid_dock"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </android.support.v17.leanback.app.BrowseFrameLayout>
+</FrameLayout>
diff --git a/v17/leanback/res/raw/lb_voice_failure.ogg b/v17/leanback/res/raw/lb_voice_failure.ogg
new file mode 100644
index 0000000..d10b3d4
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_failure.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_no_input.ogg b/v17/leanback/res/raw/lb_voice_no_input.ogg
new file mode 100644
index 0000000..503eeab
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_no_input.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_open.ogg b/v17/leanback/res/raw/lb_voice_open.ogg
new file mode 100644
index 0000000..bf73679
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_open.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_success.ogg b/v17/leanback/res/raw/lb_voice_success.ogg
new file mode 100644
index 0000000..06afa23
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_success.ogg
Binary files differ
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
new file mode 100644
index 0000000..0c71004
--- /dev/null
+++ b/v17/leanback/res/values-af/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Soekhandeling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Soek"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Praat om te soek"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
new file mode 100644
index 0000000..148abef
--- /dev/null
+++ b/v17/leanback/res/values-am/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"እርምጃ ይፈልጉ"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ይፈልጉ"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ለመፈለግ ይናገሩ"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
new file mode 100644
index 0000000..2c00f59
--- /dev/null
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"إجراء البحث"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"بحث"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"التحدث  للبحث"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
new file mode 100644
index 0000000..2df8e3f
--- /dev/null
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Действие за търсене"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Търсете"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорете, за да търсите"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
new file mode 100644
index 0000000..1b61a08
--- /dev/null
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acció de cerca"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Cerca."</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per fer una cerca."</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
new file mode 100644
index 0000000..e8ac473
--- /dev/null
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Vyhledávání akce"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Vyhledávání"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Vyhledávejte hlasem"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
new file mode 100644
index 0000000..822d364
--- /dev/null
+++ b/v17/leanback/res/values-da/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Søgehandling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Søg"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tal for at søge"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
new file mode 100644
index 0000000..7a89cd2
--- /dev/null
+++ b/v17/leanback/res/values-de/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Suchvorgang"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Suchen"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Zum Suchen sprechen"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
new file mode 100644
index 0000000..ea4ef68
--- /dev/null
+++ b/v17/leanback/res/values-el/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ενέργεια αναζήτησης"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Αναζήτηση"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Μιλήστε για να κάνετε αναζήτηση"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..634e960
--- /dev/null
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..634e960
--- /dev/null
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..cb9efbc
--- /dev/null
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acción de búsqueda"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Búsqueda"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
new file mode 100644
index 0000000..bdbb604
--- /dev/null
+++ b/v17/leanback/res/values-es/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Buscar..."</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Buscar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Buscar por voz"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..c54283e
--- /dev/null
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Otsimistoiming"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Otsing"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Öelge otsimiseks"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
new file mode 100644
index 0000000..2cfbfe2
--- /dev/null
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"عملکرد جستجو"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"جستجو"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"برای جستجو صحبت کنید"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
new file mode 100644
index 0000000..eb98cf0
--- /dev/null
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Hakutoiminto"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Haku"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tee haku puhumalla"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..c474187
--- /dev/null
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Action de recherche"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncez votre recherche"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
new file mode 100644
index 0000000..f619d5a
--- /dev/null
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Commande de recherche"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncer la recherche"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
new file mode 100644
index 0000000..0b831bc
--- /dev/null
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"खोज कार्रवाई"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"खोजें"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजने के लिए बोलें"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
new file mode 100644
index 0000000..2bbde0a
--- /dev/null
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Radnja pretraživanja"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite upit za pretraživanje"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
new file mode 100644
index 0000000..2ff0219
--- /dev/null
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Keresési művelet"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Keresés"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Beszéljen a keresés indításához"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..46ce4a2
--- /dev/null
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Որոնման հրամանը"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Որոնում"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Խոսեք՝ որոնելու համար"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
new file mode 100644
index 0000000..3381263
--- /dev/null
+++ b/v17/leanback/res/values-in/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tindakan Penelusuran"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Telusuri"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ucapkan untuk menelusuri"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
new file mode 100644
index 0000000..72b2522
--- /dev/null
+++ b/v17/leanback/res/values-it/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Azione di ricerca"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Ricerca"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per cercare"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
new file mode 100644
index 0000000..9547344
--- /dev/null
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"פעולת חיפוש"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"חפש"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
new file mode 100644
index 0000000..edb984e
--- /dev/null
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"検索操作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"検索"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"音声検索"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..13becd1
--- /dev/null
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ძიების მოქმედება"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ძიება"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"თქვით საძიებო ფრაზა"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..ddd19c4
--- /dev/null
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"​ស្វែងរក​សកម្មភាព"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ស្វែងរក"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"និយាយ​​ដើម្បី​ស្វែងរក"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
new file mode 100644
index 0000000..db3dcb5
--- /dev/null
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"검색 작업"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"검색"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"음성 검색"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..444c8d1
--- /dev/null
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ຊອກ​ຫາ​ຄຳ​ສັ່ງ"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ຊອກຫາ"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ເວົ້າ​ເພື່ອ​ຊອກ​ຫາ"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
new file mode 100644
index 0000000..b278038
--- /dev/null
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Paieškos veiksmas"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Paieška"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Pasakykite, kad ieškotumėte"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
new file mode 100644
index 0000000..6101131
--- /dev/null
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Meklēšanas darbība"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Meklēt"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Runāt, lai meklētu"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..8ee0ee7
--- /dev/null
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Хайлтын үйлдэл"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Хайлт"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ярьж хайх"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..8a9614b
--- /dev/null
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tindakan Carian"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Carian"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Sebut untuk mencari"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
new file mode 100644
index 0000000..e0b1921
--- /dev/null
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Søkehandling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Søk"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Snakk for å søke"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
new file mode 100644
index 0000000..39e2cbe
--- /dev/null
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Actie zoeken"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Zoeken"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Spreek om te zoeken"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
new file mode 100644
index 0000000..0e6fc33
--- /dev/null
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Wyszukaj czynność"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Szukaj"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Powiedz, aby wyszukać"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..97e0ba9
--- /dev/null
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
new file mode 100644
index 0000000..97e0ba9
--- /dev/null
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
new file mode 100644
index 0000000..1a63cfa
--- /dev/null
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acțiunea de căutare"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Căutați"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Rostiți pentru a căuta"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
new file mode 100644
index 0000000..1123b7c
--- /dev/null
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Поиск"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Поиск"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Произнесите запрос"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
new file mode 100644
index 0000000..e4b4571
--- /dev/null
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Akcia vyhľadávania"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Hľadať"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Hovorením spustíte vyhľadávanie"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
new file mode 100644
index 0000000..f7734bf
--- /dev/null
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Dejanje iskanja"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Iskanje"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite, če želite iskati"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
new file mode 100644
index 0000000..09466bc
--- /dev/null
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Радња претраге"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Претражите"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорите да бисте претраживали"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
new file mode 100644
index 0000000..2bcf232
--- /dev/null
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Sökåtgärd"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Sök"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Säg det du söker efter"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
new file mode 100644
index 0000000..c87e932
--- /dev/null
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Kitendo cha Kutafuta"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Utafutaji"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tamka ili utafute"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
new file mode 100644
index 0000000..2916724
--- /dev/null
+++ b/v17/leanback/res/values-th/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"การดำเนินการค้นหา"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ค้นหา"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"พูดเพื่อค้นหา"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
new file mode 100644
index 0000000..75bfcec
--- /dev/null
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Pagkilos sa Paghahanap"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Maghanap"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Magsalita upang maghanap"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
new file mode 100644
index 0000000..8ad45ed
--- /dev/null
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Arama İşlemi"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Ara"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Arama yapmak için konuşun"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
new file mode 100644
index 0000000..04741f6
--- /dev/null
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Команда пошуку"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Пошук"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Продиктуйте пошуковий запит"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
new file mode 100644
index 0000000..39537f7
--- /dev/null
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tác vụ tìm kiếm"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Tìm kiếm"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Nói để tìm kiếm"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..23ec7d7
--- /dev/null
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜索操作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜索"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用语音搜索"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..ce4d887
--- /dev/null
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ce4d887
--- /dev/null
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
new file mode 100644
index 0000000..d9ec45b
--- /dev/null
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -0,0 +1,27 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Isenzo sokusesha"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Sesha"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Khuluma ukuze useshe"</string>
+    <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+    <skip />
+    <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+    <skip />
+</resources>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index f31cd96..6e08c8a 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -115,6 +115,20 @@
         </attr>
     </declare-styleable>
 
+    <declare-styleable name="lbTitleView">
+        <!-- Defining color of the search affordance -->
+        <attr name="searchAffordanceColor" format="reference|color" />
+    </declare-styleable>
+
+    <declare-styleable name="lbSearchOrbView">
+        <!-- Defining icon of the search affordance -->
+        <attr name="searchOrbIcon" format="reference"/>
+        <!-- Defining color of the search affordance -->
+        <attr name="searchOrbColor" format="reference|color" />
+        <!-- Defining pulse color of the search affordance -->
+        <attr name="searchOrbBrightColor" format="reference|color" />
+    </declare-styleable>
+
     <declare-styleable name="LeanbackTheme">
 
         <!-- left padding of BrowseFragment, RowsFragment, DetailsFragment -->
@@ -138,6 +152,9 @@
         <!-- BrowseFragment Title icon style -->
         <attr name="browseTitleIconStyle" format="reference" />
 
+        <!-- BrowseFragment TitleView style -->
+        <attr name="browseTitleViewStyle" format="reference" />
+
         <!-- vertical grid style inside HeadersFragment -->
         <attr name="headersVerticalGridStyle" format="reference" />
         <!-- header style inside HeadersFragment -->
@@ -169,5 +186,16 @@
         <!-- style for a vertical grid of items -->
         <attr name="itemsVerticalGridStyle" format="reference" />
 
+        <!-- Default colors -->
+        <attr name="defaultBrandColor" format="reference|color" />
+        <attr name="defaultSearchColor" format="reference|color" />
+        <!-- Default color that search orb pulses to.  If not set, this color is determined programatically based on the defaultSearchColor -->
+        <attr name="defaultSearchBrightColor" format="reference|color" />
+
+        <!-- Style for searchOrb -->
+        <attr name="searchOrbViewStyle" format="reference"/>
+        <attr name="defaultSearchIcon" format="reference" />
     </declare-styleable>
+
+
 </resources>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index f78d9f3..f37b51a 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -22,7 +22,7 @@
     <color name="lb_browse_header_color">#FFFFFF</color>
 
     <color name="lb_list_item_unselected_text_color">#FFF1F1F1</color>
-    <color name="lb_background_protection">#A0333333</color>
+    <color name="lb_background_protection">#99000000</color>
 
     <color name="lb_view_dim_mask_color">#000000</color>
     <item name="lb_view_dimmed_level" type="dimen">60%</item>
@@ -40,8 +40,11 @@
     <color name="lb_speech_orb_not_recording">#33EEEEEE</color>
     <color name="lb_speech_orb_recording">#33EE0000</color>
 
-    <color name="lb_basic_card_bg_color">#FF1B1B1B</color>
-    <color name="lb_basic_card_info_bg_color">#FF1B1B1B</color>
+    <color name="lb_basic_card_bg_color">#FF3B3B3B</color>
+    <color name="lb_basic_card_info_bg_color">#FF3B3B3B</color>
     <color name="lb_basic_card_title_text_color">#FFEEEEEE</color>
-    <color name="lb_basic_card_content_text_color">#FFEEEEEE</color>
+    <color name="lb_basic_card_content_text_color">#FF888888</color>
+
+    <color name="lb_default_brand_color">#FF455A64</color>
+    <color name="lb_default_search_color">#FFFFAA3F</color>
 </resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index e4d6fc8..20cb5e8 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -22,7 +22,7 @@
     <dimen name="lb_browse_padding_right">56dp</dimen>
     <dimen name="lb_browse_padding_bottom">48dp</dimen>
     <dimen name="lb_browse_rows_margin_start">238dp</dimen>
-    <dimen name="lb_browse_rows_margin_top">140dp</dimen>
+    <dimen name="lb_browse_rows_margin_top">167dp</dimen>
     <dimen name="lb_browse_rows_fading_edge">16dp</dimen>
 
     <dimen name="lb_browse_title_height">60dp</dimen>
@@ -51,7 +51,11 @@
     <dimen name="lb_browse_row_hovercard_max_width">400dp</dimen>
     <dimen name="lb_browse_row_hovercard_title_font_size">18sp</dimen>
     <dimen name="lb_browse_row_hovercard_description_font_size">14sp</dimen>
-    <dimen name="lb_browse_item_margin">12dp</dimen>
+    <dimen name="lb_browse_item_horizontal_margin">8dp</dimen>
+    <dimen name="lb_browse_item_vertical_margin">8dp</dimen>
+    <dimen name="lb_browse_selected_row_top_padding">20dp</dimen>
+    <dimen name="lb_browse_expanded_selected_row_top_padding">16dp</dimen>
+    <dimen name="lb_browse_expanded_row_no_hovercard_bottom_padding">28dp</dimen>
 
     <item name="lb_focus_zoom_factor_small" type="fraction">106%</item>
     <item name="lb_focus_zoom_factor_medium" type="fraction">110%</item>
@@ -62,7 +66,7 @@
     <dimen name="lb_details_overview_margin_left">132dp</dimen>
     <dimen name="lb_details_overview_margin_right">132dp</dimen>
     <dimen name="lb_details_overview_margin_bottom">40dp</dimen>
-    
+
     <dimen name="lb_details_overview_description_margin_top">24dp</dimen>
     <dimen name="lb_details_overview_description_margin_left">24dp</dimen>
     <dimen name="lb_details_overview_description_margin_right">24dp</dimen>
@@ -72,8 +76,6 @@
     <dimen name="lb_details_overview_actions_padding_left">294dp</dimen>
     <dimen name="lb_details_overview_actions_padding_right">132dp</dimen>
     <dimen name="lb_details_overview_actions_height">56dp</dimen>
-    <dimen name="lb_details_overview_actions_more_margin_right">12dp</dimen>
-    <dimen name="lb_details_overview_actions_more_margin_bottom">28dp</dimen>
     <dimen name="lb_details_overview_actions_fade_size">16dp</dimen>
     <dimen name="lb_details_rows_align_top">167dp</dimen>
 
@@ -123,6 +125,9 @@
 
     <dimen name="lb_search_orb_size">52dp</dimen>
     <item name="lb_search_orb_focused_zoom" type="fraction">120%</item>
+    <item name="lb_search_orb_brightness_alpha" type="fraction">15%</item>
+    <item name="lb_search_orb_pulse_duration_ms" type="integer">1000</item>
+    <item name="lb_search_orb_scale_down_duration_ms" type="integer">100</item>
 
     <dimen name="lb_search_orb_margin_top">4dp</dimen>
     <dimen name="lb_search_orb_margin_bottom">4dp</dimen>
@@ -140,7 +145,12 @@
     <dimen name="lb_basic_card_info_height">52dp</dimen>
     <dimen name="lb_basic_card_info_padding">6dp</dimen>
     <dimen name="lb_basic_card_info_text_margin">2dp</dimen>
-    <dimen name="lb_basic_card_title_text_size">14sp</dimen>
-    <dimen name="lb_basic_card_content_text_size">10sp</dimen>
+    <dimen name="lb_basic_card_title_text_size">16sp</dimen>
+    <dimen name="lb_basic_card_content_text_size">12sp</dimen>
     <dimen name="lb_basic_card_info_badge_size">16dp</dimen>
+
+    <!-- z based shadow -->
+    <dimen name="lb_material_shadow_normal_z">2dp</dimen>
+    <dimen name="lb_material_shadow_focused_z">12dp</dimen>
+
 </resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 38e5a0b..bad8000 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -14,11 +14,15 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<resources>
-    <string name="orb_search_label">Search</string>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Image description for the call to search action visible when browsing content [CHAR LIMIT=40] -->
     <string name="orb_search_action">Search Action</string>
+    <!-- Hint showing in the empty search bar [CHAR LIMIT=40] -->
     <string name="lb_search_bar_hint">Search</string>
-    <string name="lb_search_bar_hint_speech">Search to search</string>
-    <string name="lb_search_bar_hint_with_title">Search %1$s</string>
-    <string name="lb_search_bar_hint_with_title_speech">Speak to search %1$s</string>
-</resources>
\ No newline at end of file
+    <!-- Hint showing in the empty search bar when using the voice input [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_speech">Speak to search</string>
+    <!-- Hint showing in the empty search bar using a provided context (usually the application name) [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_with_title">Search <xliff:g id="search context">%1$s</xliff:g></string>
+    <!-- Hint showing in the empty search bar using a provided context (usually the application name) while in voice input mode [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_with_title_speech">Speak to search <xliff:g id="search context">%1$s</xliff:g></string>
+</resources>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index a471def..a3a0559 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -74,6 +74,10 @@
         <item name="android:background">@color/lb_basic_card_bg_color</item>
     </style>
 
+    <style name="Widget.Leanback.TitleView" >
+        <item name="searchAffordanceColor">?attr/defaultSearchColor</item>
+    </style>
+
     <style name="Widget.Leanback.Title" />
 
     <style name="Widget.Leanback.Title.Text">
@@ -84,7 +88,7 @@
     </style>
 
     <style name="Widget.Leanback.Title.Icon">
-        <item name="android:scaleType">centerInside</item>
+        <item name="android:scaleType">fitEnd</item>
     </style>
 
     <!-- HeadersFragment -->
@@ -134,10 +138,10 @@
         <item name="android:focusableInTouchMode">true</item>
         <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
         <item name="android:paddingRight">?attr/browsePaddingRight</item>
-        <item name="android:paddingBottom">@dimen/lb_browse_item_margin</item>
-        <item name="android:paddingTop">@dimen/lb_browse_item_margin</item>
-        <item name="horizontalMargin">@dimen/lb_browse_item_margin</item>
-        <item name="verticalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="android:paddingBottom">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="android:paddingTop">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_vertical_margin</item>
         <item name="focusOutFront">true</item>
         <item name="rowHeight">wrap_content</item>
     </style>
@@ -148,11 +152,11 @@
         <item name="android:focusableInTouchMode">true</item>
         <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
         <item name="android:paddingRight">?attr/browsePaddingRight</item>
-        <item name="android:paddingBottom">@dimen/lb_browse_item_margin</item>
+        <item name="android:paddingBottom">@dimen/lb_browse_item_vertical_margin</item>
         <item name="android:paddingTop">?attr/browseRowsMarginTop</item>
         <item name="android:gravity">center_horizontal</item>
-        <item name="horizontalMargin">@dimen/lb_browse_item_margin</item>
-        <item name="verticalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_vertical_margin</item>
         <item name="focusOutFront">true</item>
     </style>
 
@@ -213,4 +217,9 @@
         <item name="android:paddingRight">@dimen/lb_action_padding_horizontal</item>
     </style>
 
+    <style name="Widget.Leanback.SearchOrbViewStyle">
+        <item name="searchOrbIcon">?attr/defaultSearchIcon</item>
+        <item name="searchOrbColor">?attr/defaultSearchColor</item>
+        <item name="searchOrbBrightColor">?attr/defaultSearchBrightColor</item>
+    </style>
 </resources>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 3024054..38284f6 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -41,14 +41,24 @@
 
         <item name="browseTitleTextStyle">@style/Widget.Leanback.Title.Text</item>
         <item name="browseTitleIconStyle">@style/Widget.Leanback.Title.Icon</item>
+        <item name="browseTitleViewStyle">@style/Widget.Leanback.TitleView</item>
+
         <item name="rowHeaderStyle">@style/Widget.Leanback.Row.Header</item>
         <item name="rowHoverCardTitleStyle">@style/Widget.Leanback.Row.HoverCardTitle</item>
         <item name="rowHoverCardDescriptionStyle">@style/Widget.Leanback.Row.HoverCardDescription</item>
 
+        <item name="searchOrbViewStyle">@style/Widget.Leanback.SearchOrbViewStyle</item>
+
         <item name="detailsDescriptionTitleStyle">@style/Widget.Leanback.DetailsDescriptionTitleStyle</item>
         <item name="detailsDescriptionSubtitleStyle">@style/Widget.Leanback.DetailsDescriptionSubtitleStyle</item>
         <item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
         <item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
+
+        <!-- TODO should be null, and set programatically to avoid dependence on leanback theme -->
+        <item name="defaultBrandColor">@color/lb_default_brand_color</item>
+        <item name="defaultSearchColor">@null</item>
+        <item name="defaultSearchBrightColor">@null</item>
+        <item name="defaultSearchIcon">@null</item>
     </style>
 
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index 8b56775..73524de 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -14,25 +14,29 @@
 package android.support.v17.leanback.app;
 
 import android.support.v17.leanback.R;
-import android.animation.ObjectAnimator;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.Matrix;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Paint;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Handler;
 import android.util.Log;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 
 /**
@@ -75,8 +79,7 @@
     private static final int FULL_ALPHA = 255;
     private static final int DIM_ALPHA_ON_SOLID = (int) (0.8f * FULL_ALPHA);
     private static final int CHANGE_BG_DELAY_MS = 500;
-    private static final int FADE_DURATION_QUICK = 200;
-    private static final int FADE_DURATION_SLOW = 1000;
+    private static final int FADE_DURATION = 500;
 
     /**
      * Using a separate window for backgrounds can improve graphics performance by
@@ -85,15 +88,6 @@
      */
     private static final boolean USE_SEPARATE_WINDOW = false;
 
-    /**
-     * If true, bitmaps will be scaled to the exact display size.
-     * Small bitmaps will be scaled up, using more memory but improving display quality.
-     * Large bitmaps will be scaled down to use less memory.
-     * Introduces an allocation overhead.
-     * TODO: support a leanback configuration option.
-     */
-    private static final boolean SCALE_BITMAPS_TO_FIT = true;
-
     private static final String WINDOW_NAME = "BackgroundManager";
     private static final String FRAGMENT_TAG = BackgroundManager.class.getCanonicalName();
 
@@ -111,12 +105,88 @@
     private int mBackgroundColor;
     private boolean mAttached;
 
-    private class DrawableWrapper {
+    private static class BitmapDrawable extends Drawable {
+
+        static class ConstantState extends Drawable.ConstantState {
+            Bitmap mBitmap;
+            Matrix mMatrix;
+            Paint mPaint;
+
+            @Override
+            public Drawable newDrawable() {
+                return new BitmapDrawable(null, mBitmap, mMatrix);
+            }
+
+            @Override
+            public int getChangingConfigurations() {
+                return 0;
+            }
+        }
+
+        private ConstantState mState = new ConstantState();
+
+        BitmapDrawable(Resources resources, Bitmap bitmap) {
+            this(resources, bitmap, null);
+        }
+
+        BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
+            mState.mBitmap = bitmap;
+            mState.mMatrix = matrix != null ? matrix : new Matrix();
+            mState.mPaint = new Paint();
+            mState.mPaint.setFilterBitmap(true);
+        }
+
+        Bitmap getBitmap() {
+            return mState.mBitmap;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            if (mState.mBitmap == null) {
+                return;
+            }
+            canvas.drawBitmap(mState.mBitmap, mState.mMatrix, mState.mPaint);
+        }
+
+        @Override
+        public int getOpacity() {
+            return android.graphics.PixelFormat.OPAQUE;
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            if (mState.mPaint.getAlpha() != alpha) {
+                mState.mPaint.setAlpha(alpha);
+                invalidateSelf();
+            }
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter cf) {
+            // Abstract in Drawable, not implemented
+        }
+
+        @Override
+        public ConstantState getConstantState() {
+            return mState;
+        }
+    }
+
+    private static class DrawableWrapper {
         protected int mAlpha;
         protected Drawable mDrawable;
-        protected ObjectAnimator mAnimator;
+        protected ValueAnimator mAnimator;
         protected boolean mAnimationPending;
 
+        private final Interpolator mInterpolator = new LinearInterpolator();
+        private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener =
+                new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setAlpha((Integer) animation.getAnimatedValue());
+            }
+        };
+
         public DrawableWrapper(Drawable drawable) {
             mDrawable = drawable;
             setAlpha(FULL_ALPHA);
@@ -145,8 +215,9 @@
             if (mAnimator != null && mAnimator.isStarted()) {
                 mAnimator.cancel();
             }
-            mAnimator = ObjectAnimator.ofInt(this, "alpha", alpha);
-            mAnimator.setInterpolator(new LinearInterpolator());
+            mAnimator = ValueAnimator.ofInt(getAlpha(), alpha);
+            mAnimator.addUpdateListener(mAnimationUpdateListener);
+            mAnimator.setInterpolator(mInterpolator);
             mAnimator.setDuration(durationMs);
             mAnimator.setStartDelay(delayMs);
             mAnimationPending = true;
@@ -158,6 +229,12 @@
             return mAnimator != null && mAnimator.isStarted();
         }
         public void startAnimation() {
+            startAnimation(null);
+        }
+        public void startAnimation(Animator.AnimatorListener listener) {
+            if (listener != null) {
+                mAnimator.addListener(listener);
+            }
             mAnimator.start();
             mAnimationPending = false;
         }
@@ -302,10 +379,11 @@
             return;
         }
         if (mLayerDrawable == null) {
-            if (DEBUG) Log.v(TAG, "onActivityResume: released state, syncing with service");
+            if (DEBUG) Log.v(TAG, "onActivityResume " + this +
+                    " released state, syncing with service");
             syncWithService();
         } else {
-            if (DEBUG) Log.v(TAG, "onActivityResume: updating service color "
+            if (DEBUG) Log.v(TAG, "onActivityResume " + this + " updating service color "
                     + mBackgroundColor + " drawable " + mBackgroundDrawable);
             mService.setColor(mBackgroundColor);
             mService.setDrawable(mBackgroundDrawable);
@@ -319,12 +397,9 @@
         if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color)
                 + " drawable " + drawable);
 
-        if (drawable != null) {
-            drawable = drawable.getConstantState().newDrawable(mContext.getResources()).mutate();
-        }
-
         mBackgroundColor = color;
-        mBackgroundDrawable = drawable;
+        mBackgroundDrawable = drawable == null ? null :
+            drawable.getConstantState().newDrawable().mutate();
 
         updateImmediate();
     }
@@ -335,7 +410,7 @@
         }
 
         mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable(
-                R.drawable.lb_background);
+                R.drawable.lb_background).mutate();
         mBgView.setBackground(mLayerDrawable);
 
         mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
@@ -394,7 +469,7 @@
      * @hide
      */
     void detach() {
-        if (DEBUG) Log.v(TAG, "detach");
+        if (DEBUG) Log.v(TAG, "detach " + this);
         release();
 
         if (mWindowManager != null && mBgView != null) {
@@ -422,7 +497,7 @@
      * inherits the current state from the continuity service.
      */
     public void release() {
-        if (DEBUG) Log.v(TAG, "release");
+        if (DEBUG) Log.v(TAG, "release " + this);
         if (mLayerDrawable != null) {
             mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
             mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
@@ -505,7 +580,11 @@
         }
         mChangeRunnable = new ChangeBackgroundRunnable(drawable);
 
-        mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
+        if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) {
+            if (DEBUG) Log.v(TAG, "animation in progress");
+        } else {
+            mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
+        }
     }
 
     /**
@@ -531,52 +610,32 @@
             return;
         }
 
-        if (mBackgroundDrawable instanceof BitmapDrawable &&
-                ((BitmapDrawable) mBackgroundDrawable).getBitmap() == bitmap) {
-            if (DEBUG) {
-                Log.v(TAG, "same bitmap detected");
-            }
-            mService.setDrawable(mBackgroundDrawable);
-            return;
-        }
+        Matrix matrix = null;
 
-        if (SCALE_BITMAPS_TO_FIT &&
-                (bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
-            // Scale proportionately to fit width and height.
-
-            Matrix matrix = new Matrix();
-
+        if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
             int dwidth = bitmap.getWidth();
             int dheight = bitmap.getHeight();
             float scale;
-            int dx;
 
-            if (DEBUG) {
-                Log.v(TAG, "original image size " + dwidth + "x" + dheight);
-            }
-
+            // Scale proportionately to fit width and height.
             if (dwidth * mHeightPx > mWidthPx * dheight) {
                 scale = (float) mHeightPx / (float) dheight;
             } else {
                 scale = (float) mWidthPx / (float) dwidth;
             }
 
-            matrix.setScale(scale, scale);
-
-            if (DEBUG) {
-                Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight());
-            }
             int subX = Math.min((int) (mWidthPx / scale), dwidth);
-            int subY = Math.min((int) (mHeightPx / scale), dheight);
-            dx = Math.max(0, (dwidth - subX) / 2);
+            int dx = Math.max(0, (dwidth - subX) / 2);
 
-            bitmap = Bitmap.createBitmap(bitmap, dx, 0, subX, subY, matrix, true);
-            if (DEBUG) {
-                Log.v(TAG, "new image size " + bitmap.getWidth() + "x" + bitmap.getHeight());
-            }
+            matrix = new Matrix();
+            matrix.setScale(scale, scale);
+            matrix.preTranslate(-dx, 0);
+
+            if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() +
+                    " scale " + scale + " dx " + dx);
         }
 
-        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);
 
         setDrawableInternal(bitmapDrawable);
     }
@@ -603,18 +662,37 @@
             mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
             if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
             mImageInWrapper.setAlpha(0);
-            mImageInWrapper.fadeIn(FADE_DURATION_SLOW, 0);
-            mImageInWrapper.startAnimation();
+            mImageInWrapper.fadeIn(FADE_DURATION, 0);
+            mImageInWrapper.startAnimation(mImageInListener);
             dimAlpha = FULL_ALPHA;
         }
 
         if (mDimWrapper != null && dimAlpha != 0) {
             if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
-            mDimWrapper.fade(FADE_DURATION_SLOW, 0, dimAlpha);
+            mDimWrapper.fade(FADE_DURATION, 0, dimAlpha);
             mDimWrapper.startAnimation();
         }
     }
 
+    private final Animator.AnimatorListener mImageInListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mChangeRunnable != null) {
+                if (DEBUG) Log.v(TAG, "animation ended, found change runnable");
+                mChangeRunnable.run();
+            }
+        }
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+    };
+
     /**
      * Returns the current background color.
      */
@@ -629,6 +707,21 @@
         return mBackgroundDrawable;
     }
 
+    private boolean sameDrawable(Drawable first, Drawable second) {
+        if (first == null || second == null) {
+            return false;
+        }
+        if (first == second) {
+            return true;
+        }
+        if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) {
+            if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Task which changes the background.
      */
@@ -652,22 +745,10 @@
         }
 
         private void runTask() {
-            boolean newBackground = false;
             lazyInit();
 
-            if (mDrawable != mBackgroundDrawable) {
-                newBackground = true;
-                if (mDrawable instanceof BitmapDrawable &&
-                        mBackgroundDrawable instanceof BitmapDrawable) {
-                    if (((BitmapDrawable) mDrawable).getBitmap() ==
-                            ((BitmapDrawable) mBackgroundDrawable).getBitmap()) {
-                        if (DEBUG) Log.v(TAG, "same underlying bitmap detected");
-                        newBackground = false;
-                    }
-                }
-            }
-
-            if (!newBackground) {
+            if (sameDrawable(mDrawable, mBackgroundDrawable)) {
+                if (DEBUG) Log.v(TAG, "same bitmap detected");
                 return;
             }
 
@@ -676,7 +757,7 @@
             if (mImageInWrapper != null) {
                 mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
                 mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha());
-                mImageOutWrapper.fadeOut(FADE_DURATION_QUICK);
+                mImageOutWrapper.fadeOut(FADE_DURATION);
 
                 // Order is important! Setting a drawable "removes" the
                 // previous one from the view
@@ -691,6 +772,8 @@
             mService.setDrawable(mBackgroundDrawable);
 
             applyBackgroundChanges();
+
+            mChangeRunnable = null;
         }
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index f0d7e89..3928ef5 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -17,16 +17,14 @@
 import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.TitleView;
 import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v7.widget.RecyclerView;
 import android.util.Log;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -38,32 +36,29 @@
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
-import android.widget.TextView;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 
-import java.util.ArrayList;
-
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 
 /**
  * Wrapper fragment for leanback browse screens. Composed of a
  * RowsFragment and a HeadersFragment.
  * <p>
- * The fragment comes with default back key support to show headers.
- * For app customized {@link Activity#onBackPressed()}, app must disable
- * BrowseFragment's default back key support by calling
- * {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and use
- * {@link BrowseFragment.BrowseTransitionListener} and {@link #startHeadersTransition(boolean)}.
+ * By default the BrowseFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * 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)}.
  */
 public class BrowseFragment extends Fragment {
 
+    @Deprecated
     public static class Params {
         private String mTitle;
         private Drawable mBadgeDrawable;
-        private int mHeadersState;
+        private int mHeadersState = HEADERS_ENABLED;
 
         /**
          * Sets the badge image.
@@ -119,16 +114,16 @@
         int mIndexOfHeadersBackStack;
 
         BackStackListener() {
-            reset();
-        }
-
-        void reset() {
             mLastEntryCount = getFragmentManager().getBackStackEntryCount();
             mIndexOfHeadersBackStack = -1;
         }
 
         @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.
@@ -150,16 +145,22 @@
     }
 
     /**
-     * Listener for browse transitions.
+     * 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) {
         }
@@ -187,15 +188,17 @@
 
     private ObjectAdapter mAdapter;
 
+    // TODO: remove Params
     private Params mParams;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private int mHeadersState = HEADERS_ENABLED;
     private int mBrandColor = Color.TRANSPARENT;
     private boolean mBrandColorSet;
 
     private BrowseFrameLayout mBrowseFrame;
-    private ImageView mBadgeView;
-    private TextView mTitleView;
-    private ViewGroup mBrowseTitle;
-    private SearchOrbView mSearchOrbView;
+    private TitleView mTitleView;
     private boolean mShowingTitle = true;
     private boolean mHeadersBackStackEnabled = true;
     private String mWithHeadersBackStackName;
@@ -233,41 +236,70 @@
         BrowseFragment.class.getCanonicalName() + ".headersState";
 
     /**
-     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     * Create arguments for a browse fragment.
+     * @deprecated Use {@link #createArgs(Bundle args, String title, int headersState)}.
      */
+    @Deprecated
     public static Bundle createArgs(Bundle args, String title, String badgeUri) {
-        return createArgs(args, title, badgeUri, HEADERS_ENABLED);
+        return createArgs(args, title, HEADERS_ENABLED);
     }
 
+    /**
+     * Create arguments for a browse fragment.
+     * @deprecated Use {@link #createArgs(Bundle args, String title, int headersState)}.
+     */
+    @Deprecated
     public static Bundle createArgs(Bundle args, String title, String badgeUri, int headersState) {
+        return createArgs(args, title, headersState);
+    }
+
+    /**
+     * Create 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.putString(ARG_BADGE_URI, badgeUri);
         args.putInt(ARG_HEADERS_STATE, headersState);
         return args;
     }
 
     /**
      * Set browse parameters.
+     * @deprecated Call methods on the fragment directly.
      */
+    @Deprecated
     public void setBrowseParams(Params params) {
         mParams = params;
-        setBadgeDrawable(mParams.mBadgeDrawable);
-        setTitle(mParams.mTitle);
-        setHeadersState(mParams.mHeadersState);
+        setBadgeDrawable(params.mBadgeDrawable);
+        setTitle(params.mTitle);
+        setHeadersState(params.mHeadersState);
     }
 
     /**
      * Returns browse parameters.
+     * @deprecated Call methods on the fragment directly.
      */
+    @Deprecated
     public Params getBrowseParams() {
         return mParams;
     }
 
     /**
-     * Sets the brand color for the browse fragment.
+     * 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(int color) {
         mBrandColor = color;
@@ -287,7 +319,14 @@
     }
 
     /**
-     * Sets the list of rows for the fragment.
+     * 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;
@@ -298,14 +337,17 @@
     }
 
     /**
-     * Returns the list of rows.
+     * Returns the adapter containing the rows for the fragment.
      */
     public ObjectAdapter getAdapter() {
         return mAdapter;
     }
 
     /**
-     * Sets an item selection listener.
+     * Sets an item selection listener. This listener will be called when an
+     * item or row is selected by a user.
+     *
+     * @param listener The listener to call when an item or row is selected.
      */
     public void setOnItemSelectedListener(OnItemSelectedListener listener) {
         mExternalOnItemSelectedListener = listener;
@@ -313,9 +355,14 @@
 
     /**
      * Sets an item clicked listener on the fragment.
-     * OnItemClickedListener 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.
+     *
+     * <p>OnItemClickedListener will override {@link View.OnClickListener} that
+     * an item presenter may set during
+     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
+     * should choose to use an {@link OnItemClickedListener} or a
+     * {@link View.OnClickListener} on your item views, but not both.
+     *
+     * @param listener The listener to call when an item is clicked.
      */
     public void setOnItemClickedListener(OnItemClickedListener listener) {
         mOnItemClickedListener = listener;
@@ -325,7 +372,7 @@
     }
 
     /**
-     * Returns the item Clicked listener.
+     * Returns the item clicked listener.
      */
     public OnItemClickedListener getOnItemClickedListener() {
         return mOnItemClickedListener;
@@ -334,29 +381,32 @@
     /**
      * Sets a click listener for the search affordance.
      *
-     * The presence of a listener will change the visibility of the search affordance in the
-     * title area. When set to non-null the title area will contain a call to search action.
+     * <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.
      *
-     * The listener onClick method will be invoked when the user click on the search action.
+     * <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.
+     * @param listener The listener to call when the search element is clicked.
      */
     public void setOnSearchClickedListener(View.OnClickListener listener) {
         mExternalOnSearchClickedListener = listener;
-        if (mSearchOrbView != null) {
-            mSearchOrbView.setOnOrbClickedListener(listener);
+        if (mTitleView != null) {
+            mTitleView.setOnSearchClickedListener(listener);
         }
     }
 
     /**
      * Sets the color used to draw the search affordance.
+     *
+     * @param color The color to use for the search affordance.
      */
     public void setSearchAffordanceColor(int color) {
         mSearchAffordanceColor = color;
         mSearchAffordanceColorSet = true;
-
-        if (mSearchOrbView != null) {
-            mSearchOrbView.setOrbColor(mSearchAffordanceColor);
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColor(mSearchAffordanceColor);
         }
     }
 
@@ -365,21 +415,24 @@
      * Can be called only after an activity has been attached.
      */
     public int getSearchAffordanceColor() {
-        if (getActivity() == null) {
-            throw new IllegalStateException("Activity must be attached");
-        }
-
         if (mSearchAffordanceColorSet) {
             return mSearchAffordanceColor;
         }
-
-        TypedValue outValue = new TypedValue();
-        getActivity().getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
-        return getResources().getColor(outValue.resourceId);
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColor();
     }
 
     /**
-     * Start headers transition.
+     * Start 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) {
@@ -392,21 +445,24 @@
     }
 
     /**
-     * Returns true if headers transition is currently running.
+     * Returns true if the headers transition is currently running.
      */
     public boolean isInHeadersTransition() {
         return mHeadersTransition != null;
     }
 
     /**
-     * Returns true if headers is showing.
+     * Returns true if headers are shown.
      */
     public boolean isShowingHeaders() {
         return mShowingHeaders;
     }
 
     /**
-     * Set listener for browse fragment transitions.
+     * Set 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;
@@ -453,6 +509,7 @@
             // If headers fragment is disabled, just return null.
             if (!mCanShowHeaders) return null;
 
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
             // if headers is running transition,  focus stays
             if (isInHeadersTransition()) return focused;
             if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
@@ -466,13 +523,13 @@
                     return focused;
                 }
                 return mRowsFragment.getVerticalGridView();
-            } else if (focused == mSearchOrbView && direction == View.FOCUS_DOWN) {
+            } else if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
                 return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
                     mRowsFragment.getVerticalGridView();
 
-            } else if (focused != mSearchOrbView && mSearchOrbView.getVisibility() == View.VISIBLE
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
                     && direction == View.FOCUS_UP) {
-                return mSearchOrbView;
+                return searchOrbView;
 
             } else {
                 return null;
@@ -510,6 +567,7 @@
                 .getInteger(R.integer.lb_browse_headers_transition_duration);
 
         readArguments(getArguments());
+
         if (mCanShowHeaders && mHeadersBackStackEnabled) {
             mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
             mBackStackChangedListener = new BackStackListener();
@@ -523,6 +581,14 @@
     }
 
     @Override
+    public void onDestroy() {
+        if (mBackStackChangedListener != null) {
+            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+        }
+        super.onDestroy();
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
@@ -537,6 +603,9 @@
             mRowsFragment = (RowsFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_container_dock);
         }
+
+        mHeadersFragment.setHeadersGone(!mCanShowHeaders);
+
         mRowsFragment.setAdapter(mAdapter);
         if (mHeaderPresenterSelector != null) {
             mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
@@ -554,34 +623,30 @@
         mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
         mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
 
-        mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group);
-        mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge);
-        mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title);
-        mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb);
-        mSearchOrbView.setOrbColor(getSearchAffordanceColor());
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setTitle(mTitle);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColor(mSearchAffordanceColor);
+        }
         if (mExternalOnSearchClickedListener != null) {
-            mSearchOrbView.setOnOrbClickedListener(mExternalOnSearchClickedListener);
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
         }
 
-        if (mParams != null) {
-            setBadgeDrawable(mParams.mBadgeDrawable);
-            setTitle(mParams.mTitle);
-            setHeadersState(mParams.mHeadersState);
-            if (mBrandColorSet) {
-                mHeadersFragment.setBackgroundColor(mBrandColor);
-            }
+        if (mBrandColorSet) {
+            mHeadersFragment.setBackgroundColor(mBrandColor);
         }
 
         mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
             @Override
             public void run() {
-                showTitle(true);
+                TitleTransitionHelper.showTitle(mTitleView, true);
             }
         });
         mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
             @Override
             public void run() {
-                showTitle(false);
+                TitleTransitionHelper.showTitle(mTitleView, false);
             }
         });
         mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
@@ -596,10 +661,8 @@
                 showHeaders(false);
             }
         });
-        mTitleUpTransition = sTransitionHelper.createChangeBounds(false);
-        sTransitionHelper.setInterpolator(mTitleUpTransition, new DecelerateInterpolator(4));
-        mTitleDownTransition = sTransitionHelper.createChangeBounds(false);
-        sTransitionHelper.setInterpolator(mTitleDownTransition, new DecelerateInterpolator());
+        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
+        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
 
         sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true);
         sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true);
@@ -654,6 +717,12 @@
         });
     }
 
+    /**
+     * 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) {
@@ -661,15 +730,9 @@
         }
     }
 
-    private void showTitle(boolean show) {
-        MarginLayoutParams lp = (MarginLayoutParams) mBrowseTitle.getLayoutParams();
-        lp.topMargin = show ? 0 : -mBrowseTitle.getHeight();
-        mBrowseTitle.setLayoutParams(lp);
-    }
-
     private void showHeaders(boolean show) {
         if (DEBUG) Log.v(TAG, "showHeaders " + show);
-        mHeadersFragment.setHeadersVisiblity(show);
+        mHeadersFragment.setHeadersEnabled(show);
         MarginLayoutParams lp;
         View containerList;
 
@@ -724,7 +787,7 @@
             mSetSelectionRunnable.mPosition = position;
             mBrowseFrame.getHandler().post(mSetSelectionRunnable);
 
-            if (position == 0) {
+            if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
                 if (!mShowingTitle) {
                     sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
                     mShowingTitle = true;
@@ -768,18 +831,21 @@
                 && mRowsFragment.getView() != null) {
             mRowsFragment.getView().requestFocus();
         }
-        showHeaders(mCanShowHeaders && mShowingHeaders);
+        if (mCanShowHeaders) {
+            showHeaders(mShowingHeaders);
+        }
     }
 
     /**
-     * Enable/disable headers transition on back key support.  This is enabled by default.
-     * BrowseFragment will add a back stack entry when headers are showing.
-     * Headers transition on back key only works for {@link #HEADERS_ENABLED}
-     * or {@link #HEADERS_HIDDEN}.
+     * Enable/disable 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 app has its own onBackPressed() handling,
-     * app must disable this feature, app may use {@link #startHeadersTransition(boolean)}
-     * and {@link BrowseTransitionListener} in its own back stack handling.
+     * 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;
@@ -799,58 +865,93 @@
         if (args.containsKey(ARG_TITLE)) {
             setTitle(args.getString(ARG_TITLE));
         }
-
-        if (args.containsKey(ARG_BADGE_URI)) {
-            setBadgeUri(args.getString(ARG_BADGE_URI));
-        }
-
         if (args.containsKey(ARG_HEADERS_STATE)) {
             setHeadersState(args.getInt(ARG_HEADERS_STATE));
         }
     }
 
-    private void setBadgeUri(String badgeUri) {
-        // TODO - need a drawable downloader
-    }
-
-    private void setBadgeDrawable(Drawable drawable) {
-        if (mBadgeView == null) {
-            return;
-        }
-        mBadgeView.setImageDrawable(drawable);
-        if (drawable != null) {
-            mBadgeView.setVisibility(View.VISIBLE);
-            mTitleView.setVisibility(View.GONE);
-        } else {
-            mBadgeView.setVisibility(View.GONE);
-            mTitleView.setVisibility(View.VISIBLE);
+    /**
+     * Sets the drawable displayed in the browse fragment title.
+     *
+     * @param drawable The Drawable to display in the browse fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (mBadgeDrawable != drawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
         }
     }
 
-    private void setTitle(String title) {
+    /**
+     * Returns the badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the browse fragment.
+     *
+     * @param title The title of the browse fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
         if (mTitleView != null) {
-            mTitleView.setText(title);
+            mTitleView.setTitle(title);
         }
     }
 
-    private void setHeadersState(int headersState) {
-        if (DEBUG) Log.v(TAG, "setHeadersState " + 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;
+    /**
+     * Returns the title for the browse fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * 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;
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index 9f426d3..4359f0c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -15,12 +15,10 @@
 
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnChildSelectedListener;
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.VerticalGridView;
-import android.util.Log;
 import android.app.Fragment;
 import android.os.Bundle;
 import android.view.LayoutInflater;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
index 78f4258..5d71b2e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -14,27 +14,21 @@
 
 package android.support.v17.leanback.app;
 
-import android.graphics.Color;
 import android.os.Bundle;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.FocusHighlightHelper;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.RowHeaderPresenter;
 import android.support.v17.leanback.widget.SinglePresenterSelector;
 import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
 import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnLayoutChangeListener;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.widget.FrameLayout;
 
 /**
  * An internal fragment containing a list of row headers.
@@ -47,7 +41,8 @@
 
     private OnItemSelectedListener mOnItemSelectedListener;
     private OnHeaderClickedListener mOnHeaderClickedListener;
-    private boolean mShow = true;
+    private boolean mHeadersEnabled = true;
+    private boolean mHeadersGone = false;
     private int mBackgroundColor;
     private boolean mBackgroundColorSet;
 
@@ -72,6 +67,8 @@
             if (position >= 0) {
                 Row row = (Row) getAdapter().get(position);
                 mOnItemSelectedListener.onItemSelected(null, row);
+            } else {
+                mOnItemSelectedListener.onItemSelected(null, null);
             }
         }
     }
@@ -91,7 +88,11 @@
             });
             headerView.setFocusable(true);
             headerView.setFocusableInTouchMode(true);
-            headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+            if (mWrapper != null) {
+                viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
+            } else {
+                headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+            }
         }
 
     };
@@ -121,22 +122,46 @@
             FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
         }
         listView.setBackgroundColor(getBackgroundColor());
+        listView.setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+        listView.setLayoutEnabled(mHeadersEnabled);
     }
 
-    void setHeadersVisiblity(boolean show) {
-        mShow = show;
+    void setHeadersEnabled(boolean enabled) {
+        mHeadersEnabled = enabled;
         final VerticalGridView listView = getVerticalGridView();
         if (listView != null) {
-            listView.setLayoutEnabled(show);
+            listView.setLayoutEnabled(mHeadersEnabled);
         }
     }
 
+    void setHeadersGone(boolean gone) {
+        mHeadersGone = gone;
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            listView.setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    // Wrapper needed because of conflict between RecyclerView's use of alpha
+    // for ADD animations, and RowHeaderPresnter's use of alpha for selected level.
+    private 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 FrameLayout(root.getContext());
+        }
+    };
     @Override
     protected void updateAdapter() {
         super.updateAdapter();
         ItemBridgeAdapter adapter = getBridgeAdapter();
         if (adapter != null) {
             adapter.setAdapterListener(mAdapterListener);
+            adapter.setWrapper(mWrapper);
         }
         if (adapter != null && getVerticalGridView() != null) {
             FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
@@ -162,7 +187,7 @@
         }
 
         TypedValue outValue = new TypedValue();
-        getActivity().getTheme().resolveAttribute(android.R.attr.colorBackground, outValue, true);
+        getActivity().getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
         return getResources().getColor(outValue.resourceId);
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index 42139b6..d41379e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -13,6 +13,8 @@
  */
 package android.support.v17.leanback.app;
 
+import java.util.ArrayList;
+
 import android.animation.TimeAnimator;
 import android.animation.TimeAnimator.TimeListener;
 import android.graphics.Canvas;
@@ -20,11 +22,12 @@
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -139,6 +142,9 @@
     int mSelectAnimatorDuration;
     Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
 
+    private RecyclerView.RecycledViewPool mRecycledViewPool;
+    private ArrayList<Presenter> mPresenterMapper;
+
     /**
      * Sets an item clicked listener on the fragment.
      * OnItemClickedListener will override {@link View.OnClickListener} that
@@ -235,6 +241,9 @@
         // Need set this for directly using RowsFragment.
         getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
         getVerticalGridView().addItemDecoration(mItemDecoration);
+
+        mRecycledViewPool = null;
+        mPresenterMapper = null;
     }
 
     @Override
@@ -251,7 +260,7 @@
             final int count = parent.getChildCount();
             for (int i = 0; i < count; i++) {
                 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                        parent.getViewHolderForChildAt(i);
+                        parent.getChildViewHolder(parent.getChildAt(i));
                 RowViewHolderExtra extra = (RowViewHolderExtra) ibvh.getExtraObject();
                 extra.drawDimForSelection(c);
             }
@@ -277,16 +286,16 @@
     private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
             new ItemBridgeAdapter.AdapterListener() {
         @Override
-        public void onAddPresenter(Presenter presenter) {
+        public void onAddPresenter(Presenter presenter, int type) {
             ((RowPresenter) presenter).setOnItemClickedListener(mOnItemClickedListener);
         }
         @Override
         public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
-            Presenter rowPresenter = vh.getPresenter();
             VerticalGridView listView = getVerticalGridView();
             if (listView != null && ((RowPresenter) vh.getPresenter()).canDrawOutOfBounds()) {
                 listView.setClipChildren(false);
             }
+            setupSharedViewPool(vh.getViewHolder());
             mViewsCreated = true;
             vh.setExtraObject(new RowViewHolderExtra(vh));
             // selected state is initialized to false, then driven by grid view onChildSelected
@@ -312,6 +321,26 @@
         }
     };
 
+    private void setupSharedViewPool(Presenter.ViewHolder viewHolder) {
+        if (viewHolder instanceof ListRowPresenter.ViewHolder) {
+            HorizontalGridView view = ((ListRowPresenter.ViewHolder) viewHolder).getGridView();
+            // Recycled view pool is shared between all list rows
+            if (mRecycledViewPool == null) {
+                mRecycledViewPool = view.getRecycledViewPool();
+            } else {
+                view.setRecycledViewPool(mRecycledViewPool);
+            }
+
+            ItemBridgeAdapter bridgeAdapter =
+                    ((ListRowPresenter.ViewHolder) viewHolder).getBridgeAdapter();
+            if (mPresenterMapper == null) {
+                mPresenterMapper = bridgeAdapter.getPresenterMapper();
+            } else {
+                bridgeAdapter.setPresenterMapper(mPresenterMapper);
+            }
+        }
+    }
+
     @Override
     protected void updateAdapter() {
         super.updateAdapter();
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
index 53d6011..e8ea78b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -17,6 +17,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
+import android.speech.SpeechRecognizer;
 import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
@@ -30,8 +31,12 @@
 import android.widget.FrameLayout;
 import android.support.v17.leanback.R;
 
+import java.util.List;
+
 /**
- * A fragment to handle searches
+ * <p>A fragment to handle searches.</p>
+ *
+ * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
  */
 public class SearchFragment extends Fragment {
     private static final String TAG = SearchFragment.class.getSimpleName();
@@ -62,6 +67,9 @@
          * <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>
          *
+         * <p>This method might not always be called before onQueryTextSubmit gets called, in
+         * particular for voice input cases.</p>
+         *
          * @param newQuery The current search query.
          * @return whether the results changed or not.
          */
@@ -91,6 +99,8 @@
     private String mTitle;
     private Drawable mBadgeDrawable;
 
+    private SpeechRecognizer mSpeechRecognizer;
+
     /**
      * @param args Bundle to use for the arguments, if null a new Bundle will be created.
      */
@@ -222,6 +232,25 @@
         list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
     }
 
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (null == mSpeechRecognizer) {
+            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
+            mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (null != mSpeechRecognizer) {
+            mSearchBar.setSpeechRecognizer(null);
+            mSpeechRecognizer.destroy();
+            mSpeechRecognizer = null;
+        }
+        super.onPause();
+    }
+
     /**
      * Set the search provider, which is responsible for returning items given
      * a search term
@@ -288,6 +317,14 @@
         return null;
     }
 
+    /**
+     * Display the completions shown by the IME.
+     *
+     * @param completions list of completions shown in the IME, can be null or empty to clear them
+     */
+    public void displayCompletions(List<String> completions) {
+        mSearchBar.displayCompletions(completions);
+    }
 
     private void retrieveResults(String searchQuery) {
         if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
diff --git a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
new file mode 100644
index 0000000..288c9eb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
@@ -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.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.widget.TitleView;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+class TitleTransitionHelper {
+
+    private static Interpolator createTransitionInterpolatorUp() {
+        return new DecelerateInterpolator(4);
+    }
+
+    private static Interpolator createTransitionInterpolatorDown() {
+        return new DecelerateInterpolator();
+    }
+
+    static public Object createTransitionTitleUp(TransitionHelper helper) {
+        Object transition = helper.createChangeBounds(false);
+        helper.setInterpolator(transition, createTransitionInterpolatorUp());
+        return transition;
+    }
+
+    static public Object createTransitionTitleDown(TransitionHelper helper) {
+        Object transition = helper.createChangeBounds(false);
+        helper.setInterpolator(transition, createTransitionInterpolatorDown());
+        return transition;
+    }
+
+    static public void showTitle(TitleView view, boolean show) {
+        MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
+        lp.topMargin = show ? 0 : -view.getHeight();
+        view.setLayoutParams(lp);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 4de12cf..1bee762 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -15,6 +15,7 @@
 
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.TitleView;
 import android.support.v17.leanback.widget.VerticalGridPresenter;
 import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.OnItemClickedListener;
@@ -27,6 +28,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -40,7 +42,12 @@
     private static final String TAG = "VerticalGridFragment";
     private static boolean DEBUG = false;
 
+    // TODO: remove Params
     private Params mParams;
+
+    private BrowseFrameLayout mBrowseFrame;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
     private ObjectAdapter mAdapter;
     private VerticalGridPresenter mGridPresenter;
     private VerticalGridPresenter.ViewHolder mGridViewHolder;
@@ -49,18 +56,19 @@
     private View.OnClickListener mExternalOnSearchClickedListener;
     private int mSelectedPosition = -1;
 
-    private ImageView mBadgeView;
-    private TextView mTitleView;
-    private ViewGroup mBrowseTitle;
-    private SearchOrbView mSearchOrbView;
+    private TitleView mTitleView;
+    private int mSearchAffordanceColor;
+    private boolean mSearchAffordanceColorSet;
     private boolean mShowingTitle = true;
 
     // transition related
     private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
-    private Object mTitleTransition;
+    private Object mTitleUpTransition;
+    private Object mTitleDownTransition;
     private Object mSceneWithTitle;
     private Object mSceneWithoutTitle;
 
+    @Deprecated
     public static class Params {
         private String mTitle;
         private Drawable mBadgeDrawable;
@@ -96,7 +104,9 @@
 
     /**
      * Set fragment parameters.
+     * @deprecated Use methods on the fragment directly.
      */
+    @Deprecated
     public void setParams(Params params) {
         mParams = params;
         setBadgeDrawable(mParams.mBadgeDrawable);
@@ -105,13 +115,51 @@
 
     /**
      * Returns fragment parameters.
+     * @deprecated Use methods on the fragment directly.
      */
+    @Deprecated
     public Params getParams() {
         return mParams;
     }
 
     /**
-     * Set the grid presenter.
+     * Sets the badge drawable displayed in the title area.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (drawable != mBadgeDrawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setTitle(mTitle);
+        }
+    }
+
+    /**
+     * Returns the title for the fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the grid presenter.
      */
     public void setGridPresenter(VerticalGridPresenter gridPresenter) {
         if (gridPresenter == null) {
@@ -170,11 +218,11 @@
             if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
                 // if has no sibling in front of it,  show title
                 if (!mShowingTitle) {
-                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleTransition);
+                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
                     mShowingTitle = true;
                 }
             } else if (mShowingTitle) {
-                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleTransition);
+                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
                 mShowingTitle = false;
             }
             mSelectedPosition = position;
@@ -210,67 +258,93 @@
      */
     public void setOnSearchClickedListener(View.OnClickListener listener) {
         mExternalOnSearchClickedListener = listener;
-        if (mSearchOrbView != null) {
-            mSearchOrbView.setOnOrbClickedListener(listener);
-        }
-    }
-
-    private void setBadgeDrawable(Drawable drawable) {
-        if (mBadgeView == null) {
-            return;
-        }
-        mBadgeView.setImageDrawable(drawable);
-        if (drawable != null) {
-            mBadgeView.setVisibility(View.VISIBLE);
-        } else {
-            mBadgeView.setVisibility(View.GONE);
-        }
-    }
-
-    private void setTitle(String title) {
         if (mTitleView != null) {
-            mTitleView.setText(title);
+            mTitleView.setOnSearchClickedListener(listener);
         }
     }
 
+    /**
+     * Sets the color used to draw the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        mSearchAffordanceColor = color;
+        mSearchAffordanceColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColor(mSearchAffordanceColor);
+        }
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColor;
+        }
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColor();
+    }
+
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
+            if (focused == searchOrbView && (
+                    direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
+                return mGridViewHolder.view;
+
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+
+            } else {
+                return null;
+            }
+        }
+    };
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
                 container, false);
 
-        mBrowseTitle = (ViewGroup) root.findViewById(R.id.browse_title_group);
-        mBadgeView = (ImageView) mBrowseTitle.findViewById(R.id.browse_badge);
-        mTitleView = (TextView) mBrowseTitle.findViewById(R.id.browse_title);
-        mSearchOrbView = (SearchOrbView) mBrowseTitle.findViewById(R.id.browse_orb);
-        if (mExternalOnSearchClickedListener != null) {
-            mSearchOrbView.setOnOrbClickedListener(mExternalOnSearchClickedListener);
-        }
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
 
-        if (mParams != null) {
-            setBadgeDrawable(mParams.mBadgeDrawable);
-            setTitle(mParams.mTitle);
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        mTitleView.setTitle(mTitle);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColor(mSearchAffordanceColor);
+        }
+        if (mExternalOnSearchClickedListener != null) {
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
         }
 
         mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
             @Override
             public void run() {
-                mBrowseTitle.setVisibility(View.VISIBLE);
+                TitleTransitionHelper.showTitle(mTitleView, true);
             }
         });
         mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
             @Override
             public void run() {
-                mBrowseTitle.setVisibility(View.GONE);
+                TitleTransitionHelper.showTitle(mTitleView, false);
             }
         });
-        mTitleTransition = sTransitionHelper.createTransitionSet(false);
-        Object fade = sTransitionHelper.createFadeTransition(
-                TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
-        Object changeBounds = sTransitionHelper.createChangeBounds(false);
-        sTransitionHelper.addTransition(mTitleTransition, fade);
-        sTransitionHelper.addTransition(mTitleTransition, changeBounds);
-        sTransitionHelper.excludeChildren(mTitleTransition, R.id.browse_grid_dock, true);
+        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
+        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
+        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_grid_dock, true);
+        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_grid_dock, true);
+
         return root;
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
index ea72cb7..1d9ff8f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
@@ -125,7 +125,7 @@
             hasTitle = false;
         } else {
             vh.mTitle.setVisibility(View.VISIBLE);
-            vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() -
+            vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() +
                     vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier());
         }
         setTopMargin(vh.mTitle, vh.mTitleMargin);
@@ -148,7 +148,7 @@
             vh.mBody.setVisibility(View.GONE);
         } else {
             vh.mBody.setVisibility(View.VISIBLE);
-            vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() -
+            vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() +
                     vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier());
 
             if (hasSubtitle) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
index a229ecc..bd87e14 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -19,8 +19,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 class ActionPresenterSelector extends PresenterSelector {
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 74bb038..79b6226 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -46,6 +46,25 @@
     }
 
     /**
+     * Search first occurrence of item in the list; return -1 if not found.
+     * @param item  The item to search in the list.
+     * @return First occurrence of item in the list or -1 if not found.
+     */
+    public int indexOf(Object item) {
+        return mItems.indexOf(item);
+    }
+
+    /**
+     * Notify content of range of items changed.  Note that this is not same
+     * as add or remove items.
+     * @param positionStart The position of first item that has changed.
+     * @param itemCount The count of how many items has changed.
+     */
+    public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
+        notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    /**
      * Adds an item to the end of the list.
      *
      * @param item The item to add to the end of the list.
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
index 20360f7..aa36d4e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -339,6 +339,8 @@
                 state = View.combineMeasuredStates(state, mainView.getMeasuredState());
             }
         }
+        setPivotX(mMeasuredWidth / 2);
+        setPivotY(mainHeight / 2);
 
 
         // The MAIN area determines the card width
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 9cd3336..7113383 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -21,7 +21,6 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 /**
  * Base class for vertically and horizontally scrolling lists. The items come
@@ -395,42 +394,6 @@
     }
 
     /**
-     * Set an interpolator for the animation when a child changes size or when 
-     * adding or removing a child.
-     * <p><i>Unstable API, might change later.</i>
-     */
-    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
-        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
-    }
-
-    /**
-     * Get the interpolator for the animation when a child changes size or when
-     * adding or removing a child.
-     * <p><i>Unstable API, might change later.</i>
-     */
-    public Interpolator getChildLayoutAnimationInterpolator() {
-        return mLayoutManager.getChildLayoutAnimationInterpolator();
-    }
-
-    /**
-     * Set the duration of the animation when a child changes size or when 
-     * adding or removing a child.
-     * <p><i>Unstable API, might change later.</i>
-     */
-    public void setChildLayoutAnimationDuration(long duration) {
-        mLayoutManager.setChildLayoutAnimationDuration(duration);
-    }
-
-    /**
-     * Get the duration of the animation when a child changes size or when 
-     * adding or removing a child.
-     * <p><i>Unstable API, might change later.</i>
-     */
-    public long getChildLayoutAnimationDuration() {
-        return mLayoutManager.getChildLayoutAnimationDuration();
-    }
-
-    /**
      * Describes how the child views are positioned. Defaults to
      * GRAVITY_TOP|GRAVITY_LEFT.
      *
@@ -504,16 +467,8 @@
     }
 
     /**
-     * Get if view has same row sibling next to it.
-     *
-     * @param position Position in adapter.
-     */
-    public boolean hasNextViewInSameRow(int position) {
-        return mLayoutManager.hasNextViewInSameRow(position);
-    }
-
-    /**
-     * Get if view has same row sibling in front of it.
+     * Returns true if the view at the given position has a same row sibling
+     * in front of it.
      *
      * @param position Position in adapter.
      */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
index 025a737..5509075 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -46,69 +46,79 @@
         final ImageView mImageView;
         final FrameLayout mDetailsDescriptionFrame;
         final HorizontalGridView mActionsRow;
-        final View mMoreActionsView;
         Presenter.ViewHolder mDetailsDescriptionViewHolder;
+        int mNumItems;
+        boolean mShowMoreRight;
+        boolean mShowMoreLeft;
 
-        class ScrollListener implements RecyclerView.OnScrollListener {
-            ObjectAdapter mAdapter;
-            boolean mShowMoreRight;
-            boolean mShowMoreLeft;
+        void bind(ItemBridgeAdapter bridgeAdapter) {
+            mNumItems = bridgeAdapter.getItemCount();
+            bridgeAdapter.setAdapterListener(mAdapterListener);
 
-            void bind(ObjectAdapter adapter) {
-                mAdapter = adapter;
+            mShowMoreRight = false;
+            mShowMoreLeft = true;
+            showMoreLeft(false);
+        }
 
-                mMoreActionsView.setAlpha(0f);
-                mShowMoreRight = false;
-                showMoreRight(true);
+        final ItemBridgeAdapter.AdapterListener mAdapterListener =
+                new ItemBridgeAdapter.AdapterListener() {
 
-                mShowMoreLeft = true;
-                showMoreLeft(false);
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                checkFirstAndLastPosition(false);
             }
+            @Override
+            public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                checkFirstAndLastPosition(false);
+            }
+        };
+
+        final RecyclerView.OnScrollListener mScrollListener =
+                new RecyclerView.OnScrollListener() {
 
             @Override
             public void onScrollStateChanged(int newState) {
             }
-
             @Override
             public void onScrolled(int dx, int dy) {
-                View view;
-                int position;
-
-                view = mActionsRow.getChildAt(mActionsRow.getChildCount() - 1);
-                position = mActionsRow.getChildViewHolder(view).getPosition();
-                if (position < (mAdapter.size() - 1) || view.getRight() > mActionsRow.getWidth()) {
-                    showMoreRight(true);
-                } else {
-                    showMoreRight(false);
-                }
-
-                view = mActionsRow.getChildAt(0);
-                position = mActionsRow.getChildViewHolder(view).getPosition();
-                if (position != 0 || view.getLeft() < 0) {
-                    showMoreLeft(true);
-                } else {
-                    showMoreLeft(false);
-                }
+                checkFirstAndLastPosition(true);
             }
+        };
 
-            private void showMoreLeft(boolean show) {
-                if (show != mShowMoreLeft) {
-                    mActionsRow.setFadingLeftEdge(show);
-                    mShowMoreLeft = show;
-                }
-            }
+        private int getViewCenter(View view) {
+            return (view.getRight() - view.getLeft()) / 2;
+        }
 
-            private void showMoreRight(boolean show) {
-                if (show != mShowMoreRight) {
-                    mMoreActionsView.animate().alpha(show ? 1f : 0).setDuration(
-                            MORE_ACTIONS_FADE_MS).start();
-                    mActionsRow.setFadingRightEdge(show);
-                    mShowMoreRight = show;
-                }
+        private void checkFirstAndLastPosition(boolean fromScroll) {
+            RecyclerView.ViewHolder viewHolder;
+
+            viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1);
+            boolean showRight = (viewHolder == null ||
+                    viewHolder.itemView.getRight() > mActionsRow.getWidth());
+
+            viewHolder = mActionsRow.findViewHolderForPosition(0);
+            boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0);
+
+            if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll +
+                    " showRight " + showRight + " showLeft " + showLeft);
+
+            showMoreRight(showRight);
+            showMoreLeft(showLeft);
+        }
+
+        private void showMoreLeft(boolean show) {
+            if (show != mShowMoreLeft) {
+                mActionsRow.setFadingLeftEdge(show);
+                mShowMoreLeft = show;
             }
         }
 
-        final ScrollListener mScrollListener = new ScrollListener();
+        private void showMoreRight(boolean show) {
+            if (show != mShowMoreRight) {
+                mActionsRow.setFadingRightEdge(show);
+                mShowMoreRight = show;
+            }
+        }
 
         public ViewHolder(View rootView) {
             super(rootView);
@@ -122,11 +132,7 @@
             final int fadeLength = rootView.getResources().getDimensionPixelSize(
                     R.dimen.lb_details_overview_actions_fade_size);
             mActionsRow.setFadingRightEdgeLength(fadeLength);
-            mActionsRow.setFadingRightEdgeOffset(-fadeLength);
             mActionsRow.setFadingLeftEdgeLength(fadeLength);
-            mActionsRow.setFadingLeftEdgeOffset(-fadeLength);
-
-            mMoreActionsView = rootView.findViewById(R.id.details_overview_actions_more);
         }
     }
 
@@ -195,7 +201,7 @@
 
     private int getDefaultBackgroundColor(Context context) {
         TypedValue outValue = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorBackground, outValue, true);
+        context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
         return context.getResources().getColor(outValue.resourceId);
     }
 
@@ -209,22 +215,26 @@
             mDetailsPresenter.onCreateViewHolder(vh.mDetailsDescriptionFrame);
         vh.mDetailsDescriptionFrame.addView(vh.mDetailsDescriptionViewHolder.view);
 
-        initDetailsOverview(v.findViewById(R.id.details_overview));
+        initDetailsOverview(vh);
 
         return vh;
     }
 
-    private void initDetailsOverview(View view) {
+    private void initDetailsOverview(ViewHolder vh) {
         int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large :
             R.dimen.lb_details_overview_height_small;
 
-        ViewGroup.LayoutParams lp = view.getLayoutParams();
-        lp.height = view.getResources().getDimensionPixelSize(resId);
-        view.setLayoutParams(lp);
+        View overview = vh.view.findViewById(R.id.details_overview);
+        ViewGroup.LayoutParams lp = overview.getLayoutParams();
+        lp.height = overview.getResources().getDimensionPixelSize(resId);
+        overview.setLayoutParams(lp);
 
-        view.setBackgroundColor(mBackgroundColorSet ?
-                mBackgroundColor : getDefaultBackgroundColor(view.getContext()));
+        overview.setBackgroundColor(mBackgroundColorSet ?
+                mBackgroundColor : getDefaultBackgroundColor(overview.getContext()));
 
+        // Max width to make a square
+        ImageView image = (ImageView) vh.view.findViewById(R.id.details_overview_image);
+        image.setMaxWidth(lp.height);
     }
 
     @Override
@@ -243,10 +253,11 @@
         mActionBridgeAdapter.clear();
         ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
         aoa.addAll(0, (Collection)row.getActions());
-        vh.mScrollListener.bind(aoa);
 
         mActionBridgeAdapter.setAdapter(aoa);
         vh.mActionsRow.setAdapter(mActionBridgeAdapter);
+
+        vh.bind(mActionBridgeAdapter);
     }
 
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index 569e264..4afb3e9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -13,21 +13,13 @@
  */
 package android.support.v17.leanback.widget;
 
-import android.graphics.drawable.TransitionDrawable;
 import android.support.v17.leanback.R;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.animation.TimeAnimator;
 import android.content.res.Resources;
 
-import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_NONE;
-import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_SMALL;
-import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_MEDIUM;
-import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_LARGE;
-
-
 /**
  * Setup the behavior how to highlight when a item gains focus.
  */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 2ef041d..c5644e1 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -15,9 +15,10 @@
 
 import android.animation.TimeAnimator;
 import android.content.Context;
+import android.graphics.PointF;
 import android.graphics.Rect;
+import android.support.v7.widget.LinearSmoothScroller;
 import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Adapter;
 import android.support.v7.widget.RecyclerView.Recycler;
 import android.support.v7.widget.RecyclerView.State;
 
@@ -47,10 +48,9 @@
 
      /*
       * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
-      * The class currently does three internal jobs:
+      * The class currently does two internal jobs:
       * - Saves optical bounds insets.
       * - Caches focus align view center.
-      * - Manages child view layout animation.
       */
     static class LayoutParams extends RecyclerView.LayoutParams {
 
@@ -67,22 +67,6 @@
         private int mAlignX;
         private int mAlignY;
 
-        // For animations
-        private TimeAnimator mAnimator;
-        private long mDuration;
-        private boolean mFirstAttachedInLayout;
-        // current virtual view position (scrollOffset + left/top) in the GridLayoutManager
-        private int mViewX, mViewY;
-        private int mSize;
-        // animation start value of translation x and y
-        private float mAnimationStartTranslationX, mAnimationStartTranslationY;
-
-        // The direction this view should be laid out along the primary dimension.
-        private boolean mLayoutForward;
-
-        // Orientation of the grid layout
-        private int mOrientation;
-
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
         }
@@ -107,14 +91,6 @@
             super(source);
         }
 
-        void onViewAttached(boolean inLayout) {
-            mFirstAttachedInLayout = inLayout;
-        }
-
-        void onViewDetached() {
-            endAnimate();
-        }
-
         int getAlignX() {
             return mAlignX;
         }
@@ -178,88 +154,6 @@
             mBottomInset = bottomInset;
         }
 
-        private TimeAnimator.TimeListener mTimeListener = new TimeAnimator.TimeListener() {
-            @Override
-            public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
-                if (mView == null) {
-                    return;
-                }
-                if (totalTime >= mDuration) {
-                    endAnimate();
-                } else {
-                    float fraction = (float) (totalTime / (double)mDuration);
-                    float fractionToEnd = 1 - mAnimator
-                        .getInterpolator().getInterpolation(fraction);
-                    if (mOrientation == HORIZONTAL) {
-                        if (mLayoutForward) {
-                            mView.setTranslationX(fractionToEnd * mAnimationStartTranslationX);
-                        } else {
-                            mView.setTranslationX(fractionToEnd * mAnimationStartTranslationX -
-                                    fractionToEnd * (mView.getMeasuredWidth() - mSize));
-                        }
-                    } else {
-                        if (mLayoutForward) {
-                            mView.setTranslationY(fractionToEnd * mAnimationStartTranslationY);
-                        } else {
-                            mView.setTranslationY(fractionToEnd * mAnimationStartTranslationY -
-                                    fractionToEnd * (mView.getMeasuredHeight() - mSize));
-                        }
-                    }
-                    invalidateItemDecoration();
-                }
-            }
-        };
-
-        void recordStart(int orientation, View view) {
-            mViewX = getOpticalLeft(view);
-            mViewY = getOpticalTop(view);
-            mOrientation = orientation;
-            mSize = mOrientation == HORIZONTAL ? view.getMeasuredWidth() : view.getMeasuredHeight();
-        }
-
-        void startAnimate(View view,
-                long duration, long startDelay, Interpolator interpolator) {
-            if (mFirstAttachedInLayout) {
-                // if the view is just attached in layout pass, do not run animation
-                mFirstAttachedInLayout = false;
-                return;
-            }
-            int newViewX = getOpticalLeft(view);
-            int newViewY = getOpticalTop(view);
-            if (newViewX != mViewX || newViewY != mViewY) {
-                if (mAnimator == null) {
-                    mAnimator = new TimeAnimator();
-                    mAnimator.setTimeListener(mTimeListener);
-                }
-                mView = view;
-                mAnimationStartTranslationX = mView.getTranslationX();
-                mAnimationStartTranslationY = mView.getTranslationY();
-                mAnimator.cancel();
-                mAnimationStartTranslationX += mViewX - newViewX;
-                mAnimationStartTranslationY += mViewY - newViewY;
-                mDuration = duration;
-                mAnimator.setDuration(mDuration);
-                mAnimator.setInterpolator(interpolator);
-                mAnimator.setStartDelay(startDelay);
-                mAnimator.start();
-                // put it at initial location
-                mTimeListener.onTimeUpdate(mAnimator, 0, 0);
-            }
-        }
-
-        void endAnimate() {
-            if (mAnimator != null) {
-                mAnimator.end();
-            }
-            if (mView != null) {
-                mSize = mOrientation == HORIZONTAL ?
-                    mView.getMeasuredWidth() : mView.getMeasuredHeight();
-                mView.setTranslationX(0);
-                mView.setTranslationY(0);
-                mView = null;
-            }
-        }
-
         private void invalidateItemDecoration() {
             ViewParent parent = mView.getParent();
             if (parent instanceof RecyclerView) {
@@ -288,7 +182,7 @@
      */
     private int mOrientation = HORIZONTAL;
 
-    private RecyclerView.Adapter mAdapter;
+    private RecyclerView.State mState;
     private RecyclerView.Recycler mRecycler;
 
     private boolean mInLayout = false;
@@ -439,15 +333,16 @@
      */
     private boolean mPruneChild = true;
 
-    /**
-     * Interpolator used to animate layout of children.
-     */
-    private Interpolator mAnimateLayoutChildInterpolator = sDefaultAnimationChildLayoutInterpolator;
+    private int[] mTempDeltas = new int[2];
+
+    private boolean mUseDeltaInPreLayout;
+
+    private int mDeltaInPreLayout, mDeltaSecondaryInPreLayout;
 
     /**
-     * Duration used to animate layout of children.
+     * Temporaries used for measuring.
      */
-    private long mAnimateLayoutChildDuration = DEFAULT_CHILD_ANIMATION_DURATION_MS;
+    private int[] mMeasuredDimension = new int[2];
 
     public GridLayoutManager(BaseGridView baseGridView) {
         mBaseGridView = baseGridView;
@@ -597,45 +492,34 @@
     }
 
     private int getPositionByView(View view) {
-        return getPositionByIndex(mBaseGridView.indexOfChild(view));
+        if (view == null) {
+            return NO_POSITION;
+        }
+        RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+        if (vh == null) {
+            return NO_POSITION;
+        }
+        return vh.getPosition();
     }
 
     private int getPositionByIndex(int index) {
-        if (index < 0) {
-            return NO_POSITION;
-        }
-        return mFirstVisiblePos + index;
-    }
-
-    private View getViewByPosition(int position) {
-        int index = getIndexByPosition(position);
-        if (index < 0) {
-            return null;
-        }
-        return getChildAt(index);
-    }
-
-    private int getIndexByPosition(int position) {
-        if (mFirstVisiblePos < 0 ||
-                position < mFirstVisiblePos || position > mLastVisiblePos) {
-            return NO_POSITION;
-        }
-        return position - mFirstVisiblePos;
+        return getPositionByView(getChildAt(index));
     }
 
     private void dispatchChildSelected() {
         if (mChildSelectedListener == null) {
             return;
         }
-
-        View view = getViewByPosition(mFocusPosition);
-
         if (mFocusPosition != NO_POSITION) {
-            mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
-                    mAdapter.getItemId(mFocusPosition));
-        } else {
-            mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+            View view = findViewByPosition(mFocusPosition);
+            if (view != null) {
+                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
+                        vh == null? NO_ID: vh.getItemId());
+                return;
+            }
         }
+        mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
     }
 
     @Override
@@ -677,11 +561,7 @@
     }
 
     protected View getViewForPosition(int position) {
-        View v = mRecycler.getViewForPosition(mAdapter, position);
-        if (v != null) {
-            ((LayoutParams) v.getLayoutParams()).onViewAttached(mInLayout);
-        }
-        return v;
+        return mRecycler.getViewForPosition(position);
     }
 
     final int getOpticalLeft(View v) {
@@ -726,13 +606,23 @@
         return p.getOpticalTop(v) + p.getAlignY();
     }
 
-    private void setViewLayoutForward(View v, boolean layoutForward) {
-        LayoutParams p = (LayoutParams) v.getLayoutParams();
-        p.mLayoutForward = layoutForward;
+    /**
+     * 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;
     }
 
-    private boolean getIsViewLayoutForward(View v) {
-        return ((LayoutParams) v.getLayoutParams()).mLayoutForward;
+    /**
+     * Discard saved Recycler and State.
+     */
+    private void leaveContext() {
+        mRecycler = null;
+        mState = null;
     }
 
     /**
@@ -743,10 +633,9 @@
      *        focus on.
      * @return Actual position that can be focused on.
      */
-    private int init(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
-            int focusPosition) {
+    private int init(int focusPosition) {
 
-        final int newItemCount = adapter.getItemCount();
+        final int newItemCount = mState.getItemCount();
 
         if (focusPosition == NO_POSITION && newItemCount > 0) {
             // if focus position is never set before,  initialize it to 0
@@ -797,7 +686,7 @@
                 }
                 // fill rows with minimal view positions of the subset
                 for (int i = firstIndex; i <= lastIndex; i++) {
-                    View v = getViewByPosition(i);
+                    View v = findViewByPosition(i);
                     if (v == null) {
                         continue;
                     }
@@ -807,25 +696,33 @@
                         mRows[row].low = mRows[row].high = low;
                     }
                 }
-                // fill other rows that does not include the subset using first item
                 int firstItemRowPosition = mRows[mGrid.getLocation(firstIndex).row].low;
                 if (firstItemRowPosition == Integer.MAX_VALUE) {
                     firstItemRowPosition = 0;
                 }
-                for (int i = 0; i < mNumRows; i++) {
-                    if (mRows[i].low == Integer.MAX_VALUE) {
-                        mRows[i].low = mRows[i].high = firstItemRowPosition;
+                if (mState.didStructureChange()) {
+                    // if there is structure change, the removed item might be in the
+                    // subset,  so it is meaningless to maintain the low locations.
+                    for (int i = 0; i < mNumRows; i++) {
+                        mRows[i].low = firstItemRowPosition;
+                        mRows[i].high = firstItemRowPosition;
+                    }
+                } else {
+                    // fill other rows that does not include the subset using first item
+                    for (int i = 0; i < mNumRows; i++) {
+                        if (mRows[i].low == Integer.MAX_VALUE) {
+                            mRows[i].low = mRows[i].high = firstItemRowPosition;
+                        }
                     }
                 }
             }
 
             // Same adapter, we can reuse any attached views
-            detachAndScrapAttachedViews(recycler);
+            detachAndScrapAttachedViews(mRecycler);
 
         } else {
             // otherwise recreate data structure
             mRows = new StaggeredGrid.Row[mNumRows];
-            mRowSizeSecondary = new int[mNumRows];
 
             for (int i = 0; i < mNumRows; i++) {
                 mRows[i] = new StaggeredGrid.Row();
@@ -838,15 +735,13 @@
             }
 
             // Adapter may have changed so remove all attached views permanently
-            removeAllViews();
+            removeAndRecycleAllViews(mRecycler);
 
             mScrollOffsetPrimary = 0;
             mScrollOffsetSecondary = 0;
             mWindowAlignment.reset();
         }
 
-        mAdapter = adapter;
-        mRecycler = recycler;
         mGrid.setProvider(mGridProvider);
         // mGrid share the same Row array information
         mGrid.setRows(mRows);
@@ -880,34 +775,63 @@
         return getRowStartSecondary(mNumRows - 1) + getRowSizeSecondary(mNumRows - 1);
     }
 
+    private void measureScrapChild(int position, int widthSpec, int heightSpec,
+            int[] measuredDimension) {
+        View view = mRecycler.getViewForPosition(position);
+        if (view != null) {
+            LayoutParams p = (LayoutParams) view.getLayoutParams();
+            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
+                    getPaddingLeft() + getPaddingRight(), p.width);
+            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
+                    getPaddingTop() + getPaddingBottom(), p.height);
+            view.measure(childWidthSpec, childHeightSpec);
+            measuredDimension[0] = view.getMeasuredWidth();
+            measuredDimension[1] = view.getMeasuredHeight();
+            mRecycler.recycleView(view);
+        }
+    }
+
     private boolean processRowSizeSecondary(boolean measure) {
         if (mFixedRowSizeSecondary != 0) {
             return false;
         }
 
-        List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
+        if (mGrid == null) {
+            if (mState.getItemCount() > 0) {
+                measureScrapChild(mFocusPosition == NO_POSITION ? 0 : mFocusPosition,
+                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                        mMeasuredDimension);
+                if (DEBUG) Log.v(TAG, "measured scrap child: " + mMeasuredDimension[0] +
+                        " " + mMeasuredDimension[1]);
+            } else {
+                mMeasuredDimension[0] = mMeasuredDimension[1] = 0;
+            }
+        }
+
+        List<Integer>[] rows = mGrid == null ? null :
+            mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
         boolean changed = false;
 
         for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
             int rowSize = 0;
 
-            final int rowItemCount = rows[rowIndex].size();
+            final int rowItemCount = rows == null ? 1 : rows[rowIndex].size();
             if (DEBUG) Log.v(getTag(), "processRowSizeSecondary row " + rowIndex +
                     " rowItemCount " + rowItemCount);
 
             for (int i = 0; i < rowItemCount; i++) {
-                final int position = rows[rowIndex].get(i);
-                final View view = getViewByPosition(position);
-                if (measure && view.isLayoutRequested()) {
-                    measureChild(view, rowIndex);
-                }
-                // If this view isn't visible, we ignore it.
-                if (getOpticalRight(view) < 0 || getOpticalLeft(view) > getWidth() ||
-                        getOpticalBottom(view) < 0 || getOpticalTop(view) > getHeight()) {
-                    continue;
+                if (rows != null) {
+                    final int position = rows[rowIndex].get(i);
+                    final View view = findViewByPosition(position);
+                    if (measure && view.isLayoutRequested()) {
+                        measureChild(view);
+                    }
+                    mMeasuredDimension[0] = view.getMeasuredWidth();
+                    mMeasuredDimension[1] = view.getMeasuredHeight();
                 }
                 final int secondarySize = mOrientation == HORIZONTAL ?
-                        view.getMeasuredHeight() : view.getMeasuredWidth();
+                        mMeasuredDimension[1] : mMeasuredDimension[0];
                 if (secondarySize > rowSize) {
                     rowSize = secondarySize;
                 }
@@ -958,7 +882,9 @@
     }
 
     @Override
-    public void onMeasure(int widthSpec, int heightSpec) {
+    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+        saveContext(recycler, state);
+
         int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
         int measuredSizeSecondary;
         if (mOrientation == HORIZONTAL) {
@@ -983,11 +909,13 @@
             mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
             mFixedRowSizeSecondary = 0;
 
-            // Measure all current children and update cached row heights
-            if (mGrid != null) {
-                processRowSizeSecondary(true);
+            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
+                mRowSizeSecondary = new int[mNumRows];
             }
 
+            // Measure all current children and update cached row heights
+            processRowSizeSecondary(true);
+
             switch (modeSecondary) {
             case MeasureSpec.UNSPECIFIED:
                 measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
@@ -1059,9 +987,11 @@
                     " mFixedRowSizeSecondary " + mFixedRowSizeSecondary +
                     " mNumRows " + mNumRows);
         }
+
+        leaveContext();
     }
 
-    private void measureChild(View child, int rowIndex) {
+    private void measureChild(View child) {
         final ViewGroup.LayoutParams lp = child.getLayoutParams();
         final int secondarySpec = (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) ?
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) :
@@ -1094,7 +1024,7 @@
 
         @Override
         public int getCount() {
-            return mAdapter.getItemCount();
+            return mState.getItemCount();
         }
 
         @Override
@@ -1110,14 +1040,16 @@
                 }
             }
 
-            if (append) {
-                addView(v);
-            } else {
-                addView(v, 0);
+            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
+            if (!((RecyclerView.LayoutParams) v.getLayoutParams()).isItemRemoved()) {
+                if (append) {
+                    addView(v);
+                } else {
+                    addView(v, 0);
+                }
+                measureChild(v);
             }
 
-            measureChild(v, rowIndex);
-
             int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
             int start, end;
             if (append) {
@@ -1163,10 +1095,12 @@
             if (DEBUG) {
                 Log.d(getTag(), "addView " + index + " " + v);
             }
-            updateScrollMin();
-            updateScrollMax();
-
-            setViewLayoutForward(v, append);
+            if (index == mFirstVisiblePos) {
+                updateScrollMin();
+            }
+            if (index == mLastVisiblePos) {
+                updateScrollMax();
+            }
         }
     };
 
@@ -1259,7 +1193,7 @@
     // Append one column if possible and return true if reach end.
     private boolean appendOneVisibleItem() {
         while (true) {
-            if (mLastVisiblePos != NO_POSITION && mLastVisiblePos < mAdapter.getItemCount() -1 &&
+            if (mLastVisiblePos != NO_POSITION && mLastVisiblePos < mState.getItemCount() -1 &&
                     mLastVisiblePos < mGrid.getLastIndex()) {
                 // append invisible view of saved location till last row
                 final int index = mLastVisiblePos + 1;
@@ -1268,9 +1202,9 @@
                 if (row == mNumRows - 1) {
                     return false;
                 }
-            } else if ((mLastVisiblePos == NO_POSITION && mAdapter.getItemCount() > 0) ||
+            } else if ((mLastVisiblePos == NO_POSITION && mState.getItemCount() > 0) ||
                     (mLastVisiblePos != NO_POSITION &&
-                            mLastVisiblePos < mAdapter.getItemCount() - 1)) {
+                            mLastVisiblePos < mState.getItemCount() - 1)) {
                 mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
                 return false;
             } else {
@@ -1318,13 +1252,12 @@
     }
 
     private void removeChildAt(int position) {
-        View v = getViewByPosition(position);
+        View v = findViewByPosition(position);
         if (v != null) {
             if (DEBUG) {
                 Log.d(getTag(), "removeAndRecycleViewAt " + position);
             }
-            ((LayoutParams) v.getLayoutParams()).onViewDetached();
-            removeAndRecycleViewAt(getIndexByPosition(position), mRecycler);
+            removeAndRecycleView(v, mRecycler);
         }
     }
 
@@ -1334,7 +1267,7 @@
         }
         boolean update = false;
         while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
-            View view = getViewByPosition(mLastVisiblePos);
+            View view = findViewByPosition(mLastVisiblePos);
             if (getViewMin(view) > mSizePrimary) {
                 removeChildAt(mLastVisiblePos);
                 mLastVisiblePos--;
@@ -1354,7 +1287,7 @@
         }
         boolean update = false;
         while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
-            View view = getViewByPosition(mFirstVisiblePos);
+            View view = findViewByPosition(mFirstVisiblePos);
             if (getViewMax(view) < 0) {
                 removeChildAt(mFirstVisiblePos);
                 mFirstVisiblePos++;
@@ -1377,7 +1310,7 @@
             mRows[i].high = Integer.MIN_VALUE;
         }
         for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
-            View view = getViewByPosition(i);
+            View view = findViewByPosition(i);
             int row = mGrid.getLocation(i).row;
             int low = getViewMin(view) + mScrollOffsetPrimary;
             if (low < mRows[row].low) {
@@ -1402,56 +1335,33 @@
             final int startSecondary = getRowStartSecondary(i) - mScrollOffsetSecondary;
             for (int j = 0, size = row.size(); j < size; j++) {
                 final int position = row.get(j);
-                final View view = getViewByPosition(position);
-                final boolean layoutForward = getIsViewLayoutForward(view);
+                final View view = findViewByPosition(position);
                 int primaryDelta, start, end;
 
                 if (mOrientation == HORIZONTAL) {
                     final int primarySize = view.getMeasuredWidth();
                     if (view.isLayoutRequested()) {
-                        measureChild(view, i);
+                        measureChild(view);
                     }
-                    if (layoutForward) {
-                        start = getViewMin(view);
-                        end = start + view.getMeasuredWidth();
-                        primaryDelta = view.getMeasuredWidth() - primarySize;
-                        if (primaryDelta != 0) {
-                            for (int k = j + 1; k < size; k++) {
-                                getViewByPosition(row.get(k)).offsetLeftAndRight(primaryDelta);
-                            }
-                        }
-                    } else {
-                        end = getViewMax(view);
-                        start = end - view.getMeasuredWidth();
-                        primaryDelta = primarySize - view.getMeasuredWidth();
-                        if (primaryDelta != 0) {
-                            for (int k = 0; k < j; k++) {
-                                getViewByPosition(row.get(k)).offsetLeftAndRight(primaryDelta);
-                            }
+                    start = getViewMin(view);
+                    end = start + view.getMeasuredWidth();
+                    primaryDelta = view.getMeasuredWidth() - primarySize;
+                    if (primaryDelta != 0) {
+                        for (int k = j + 1; k < size; k++) {
+                            findViewByPosition(row.get(k)).offsetLeftAndRight(primaryDelta);
                         }
                     }
                 } else {
                     final int primarySize = view.getMeasuredHeight();
                     if (view.isLayoutRequested()) {
-                        measureChild(view, i);
+                        measureChild(view);
                     }
-                    if (layoutForward) {
-                        start = getViewMin(view);
-                        end = start + view.getMeasuredHeight();
-                        primaryDelta = view.getMeasuredHeight() - primarySize;
-                        if (primaryDelta != 0) {
-                            for (int k = j + 1; k < size; k++) {
-                                getViewByPosition(row.get(k)).offsetTopAndBottom(primaryDelta);
-                            }
-                        }
-                    } else {
-                        end = getViewMax(view);
-                        start = end - view.getMeasuredHeight();
-                        primaryDelta = primarySize - view.getMeasuredHeight();
-                        if (primaryDelta != 0) {
-                            for (int k = 0; k < j; k++) {
-                                getViewByPosition(row.get(k)).offsetTopAndBottom(primaryDelta);
-                            }
+                    start = getViewMin(view);
+                    end = start + view.getMeasuredHeight();
+                    primaryDelta = view.getMeasuredHeight() - primarySize;
+                    if (primaryDelta != 0) {
+                        for (int k = j + 1; k < size; k++) {
+                            findViewByPosition(row.get(k)).offsetTopAndBottom(primaryDelta);
                         }
                     }
                 }
@@ -1469,19 +1379,31 @@
         updateScrollSecondAxis();
 
         if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
-            View focusView = getViewByPosition(mFocusPosition == NO_POSITION ? 0 : mFocusPosition);
+            View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 : mFocusPosition);
             scrollToView(focusView, false);
         }
     }
 
+    @Override
+    public boolean supportsItemAnimations() {
+        return mAnimateChildLayout;
+    }
+
+    private void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
+        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            removeAndRecycleViewAt(i, recycler);
+        }
+    }
+
     // Lays out items based on the current scroll position
     @Override
-    public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
-            boolean structureChanged, RecyclerView.State state) {
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
         if (DEBUG) {
             Log.v(getTag(), "layoutChildren start numRows " + mNumRows + " mScrollOffsetSecondary "
                     + mScrollOffsetSecondary + " mScrollOffsetPrimary " + mScrollOffsetPrimary
-                    + " structureChanged " + structureChanged
+                    + " inPreLayout " + state.isPreLayout()
+                    + " didStructureChange " + state.didStructureChange()
                     + " mForceFullLayout " + mForceFullLayout);
             Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
         }
@@ -1490,42 +1412,56 @@
             // haven't done measure yet
             return;
         }
-        final int itemCount = adapter.getItemCount();
+        final int itemCount = state.getItemCount();
         if (itemCount < 0) {
             return;
         }
 
         if (!mLayoutEnabled) {
             discardLayoutInfo();
-            removeAllViews();
+            removeAndRecycleAllViews(recycler);
             return;
         }
         mInLayout = true;
 
-        attemptRecordChildLayout();
+        saveContext(recycler, state);
         // Track the old focus view so we can adjust our system scroll position
         // so that any scroll animations happening now will remain valid.
+        // We must use same delta in Pre Layout (if prelayout exists) and second layout.
+        // So we cache the deltas in PreLayout and use it in second layout.
         int delta = 0, deltaSecondary = 0;
-        if (mFocusPosition != NO_POSITION
-                && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
-            View focusView = getViewByPosition(mFocusPosition);
-            if (focusView != null) {
-                delta = mWindowAlignment.mainAxis().getSystemScrollPos(
-                        getViewCenter(focusView) + mScrollOffsetPrimary) - mScrollOffsetPrimary;
-                deltaSecondary =
-                    mWindowAlignment.secondAxis().getSystemScrollPos(
-                            getViewCenterSecondary(focusView) + mScrollOffsetSecondary)
-                    - mScrollOffsetSecondary;
+        if (!state.isPreLayout() && mUseDeltaInPreLayout) {
+            delta = mDeltaInPreLayout;
+            deltaSecondary = mDeltaSecondaryInPreLayout;
+        } else {
+            if (mFocusPosition != NO_POSITION
+                    && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                View focusView = findViewByPosition(mFocusPosition);
+                if (focusView != null) {
+                    delta = mWindowAlignment.mainAxis().getSystemScrollPos(
+                            getViewCenter(focusView) + mScrollOffsetPrimary) - mScrollOffsetPrimary;
+                    deltaSecondary =
+                        mWindowAlignment.secondAxis().getSystemScrollPos(
+                                getViewCenterSecondary(focusView) + mScrollOffsetSecondary)
+                        - mScrollOffsetSecondary;
+                    if (mUseDeltaInPreLayout = state.isPreLayout()) {
+                        mDeltaInPreLayout = delta;
+                        mDeltaSecondaryInPreLayout = deltaSecondary;
+                    }
+                }
             }
         }
 
         final boolean hasDoneFirstLayout = hasDoneFirstLayout();
-        if (!structureChanged && !mForceFullLayout && hasDoneFirstLayout) {
+        int savedFocusPos = mFocusPosition;
+        boolean fastRelayout = false;
+        if (!mState.didStructureChange() && !mForceFullLayout && hasDoneFirstLayout) {
+            fastRelayout = true;
             fastRelayout();
         } else {
             boolean hadFocus = mBaseGridView.hasFocus();
 
-            int newFocusPosition = init(adapter, recycler, mFocusPosition);
+            int newFocusPosition = init(mFocusPosition);
             if (DEBUG) {
                 Log.v(getTag(), "mFocusPosition " + mFocusPosition + " newFocusPosition "
                     + newFocusPosition);
@@ -1563,7 +1499,7 @@
             do {
                 oldFirstVisible = mFirstVisiblePos;
                 oldLastVisible = mLastVisiblePos;
-                View focusView = getViewByPosition(newFocusPosition);
+                View focusView = findViewByPosition(newFocusPosition);
                 // we need force to initialize the child view's position
                 scrollToView(focusView, false);
                 if (focusView != null && hadFocus) {
@@ -1593,19 +1529,20 @@
             Log.d(getTag(), sw.toString());
         }
 
-        removeAndRecycleScrap(recycler);
-        attemptAnimateLayoutChild();
-
         if (mRowSecondarySizeRefresh) {
             mRowSecondarySizeRefresh = false;
         } else {
             updateRowSecondarySizeRefresh();
         }
 
-        if (!hasDoneFirstLayout) {
-            dispatchChildSelected();
+        if (!state.isPreLayout()) {
+            mUseDeltaInPreLayout = false;
+            if (!fastRelayout || mFocusPosition != savedFocusPos) {
+                dispatchChildSelected();
+            }
         }
         mInLayout = false;
+        leaveContext();
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
     }
 
@@ -1620,7 +1557,6 @@
                 getChildAt(i).offsetLeftAndRight(increment);
             }
         }
-        mScrollOffsetSecondary -= increment;
     }
 
     private void offsetChildrenPrimary(int increment) {
@@ -1634,33 +1570,64 @@
                 getChildAt(i).offsetLeftAndRight(increment);
             }
         }
-        mScrollOffsetPrimary -= increment;
     }
 
     @Override
     public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
-        if (DEBUG) Log.v(TAG, "scrollHorizontallyBy " + dx);
-
-        if (mOrientation == HORIZONTAL) {
-            return scrollDirectionPrimary(dx);
-        } else {
-            return scrollDirectionSecondary(dx);
+        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
+        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+            return 0;
         }
+        saveContext(recycler, state);
+        int result;
+        if (mOrientation == HORIZONTAL) {
+            result = scrollDirectionPrimary(dx);
+        } else {
+            result = scrollDirectionSecondary(dx);
+        }
+        leaveContext();
+        return result;
     }
 
     @Override
     public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
-        if (DEBUG) Log.v(TAG, "scrollVerticallyBy " + dy);
-        if (mOrientation == VERTICAL) {
-            return scrollDirectionPrimary(dy);
-        } else {
-            return scrollDirectionSecondary(dy);
+        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
+        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+            return 0;
         }
+        saveContext(recycler, state);
+        int result;
+        if (mOrientation == VERTICAL) {
+            result = scrollDirectionPrimary(dy);
+        } else {
+            result = scrollDirectionSecondary(dy);
+        }
+        leaveContext();
+        return result;
     }
 
     // scroll in main direction may add/prune views
     private int scrollDirectionPrimary(int da) {
+        if (da > 0) {
+            if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
+                int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+                if (mScrollOffsetPrimary + da > maxScroll) {
+                    da = maxScroll - mScrollOffsetPrimary;
+                }
+            }
+        } else if (da < 0) {
+            if (!mWindowAlignment.mainAxis().isMinUnknown()) {
+                int minScroll = mWindowAlignment.mainAxis().getMinScroll();
+                if (mScrollOffsetPrimary + da < minScroll) {
+                    da = minScroll - mScrollOffsetPrimary;
+                }
+            }
+        }
+        if (da == 0) {
+            return 0;
+        }
         offsetChildrenPrimary(-da);
+        mScrollOffsetPrimary += da;
         if (mInLayout) {
             return da;
         }
@@ -1693,34 +1660,104 @@
 
     // scroll in second direction will not add/prune views
     private int scrollDirectionSecondary(int dy) {
+        if (dy == 0) {
+            return 0;
+        }
         offsetChildrenSecondary(-dy);
+        mScrollOffsetSecondary += dy;
         mBaseGridView.invalidate();
         return dy;
     }
 
     private void updateScrollMax() {
-        if (mLastVisiblePos >= 0 && mLastVisiblePos == mAdapter.getItemCount() - 1) {
-            int maxEdge = Integer.MIN_VALUE;
-            for (int i = 0; i < mRows.length; i++) {
-                if (mRows[i].high > maxEdge) {
-                    maxEdge = mRows[i].high;
-                }
+        if (mLastVisiblePos < 0) {
+            return;
+        }
+        final boolean lastAvailable = mLastVisiblePos == mState.getItemCount() - 1;
+        final boolean maxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
+        if (!lastAvailable && maxUnknown) {
+            return;
+        }
+        int maxEdge = Integer.MIN_VALUE;
+        int rowIndex = -1;
+        for (int i = 0; i < mRows.length; i++) {
+            if (mRows[i].high > maxEdge) {
+                maxEdge = mRows[i].high;
+                rowIndex = i;
             }
+        }
+        int maxScroll = Integer.MAX_VALUE;
+        for (int i = mLastVisiblePos; i >= mFirstVisiblePos; i--) {
+            StaggeredGrid.Location location = mGrid.getLocation(i);
+            if (location != null && location.row == rowIndex) {
+                int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
+                mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
+                maxScroll = mWindowAlignment
+                        .mainAxis().getSystemScrollPos(mScrollOffsetPrimary
+                        + getViewCenter(findViewByPosition(i)));
+                mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
+                break;
+            }
+        }
+        if (lastAvailable) {
             mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
-            if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge);
+            mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
+            if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
+                    " scrollMax to " + maxScroll);
+        } else {
+            // the maxScroll for currently last visible item is larger,
+            // so we must invalidate the max scroll value.
+            if (maxScroll > mWindowAlignment.mainAxis().getMaxScroll()) {
+                mWindowAlignment.mainAxis().invalidateScrollMax();
+                if (DEBUG) Log.v(getTag(), "Invalidate scrollMax since it should be "
+                        + "greater than " + maxScroll);
+            }
         }
     }
 
     private void updateScrollMin() {
-        if (mFirstVisiblePos == 0) {
-            int minEdge = Integer.MAX_VALUE;
-            for (int i = 0; i < mRows.length; i++) {
-                if (mRows[i].low < minEdge) {
-                    minEdge = mRows[i].low;
-                }
+        if (mFirstVisiblePos < 0) {
+            return;
+        }
+        final boolean firstAvailable = mFirstVisiblePos == 0;
+        final boolean minUnknown = mWindowAlignment.mainAxis().isMinUnknown();
+        if (!firstAvailable && minUnknown) {
+            return;
+        }
+        int minEdge = Integer.MAX_VALUE;
+        int rowIndex = -1;
+        for (int i = 0; i < mRows.length; i++) {
+            if (mRows[i].low < minEdge) {
+                minEdge = mRows[i].low;
+                rowIndex = i;
             }
+        }
+        int minScroll = Integer.MIN_VALUE;
+        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+            StaggeredGrid.Location location = mGrid.getLocation(i);
+            if (location != null && location.row == rowIndex) {
+                int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
+                mWindowAlignment.mainAxis().setMinEdge(minEdge);
+                minScroll = mWindowAlignment
+                        .mainAxis().getSystemScrollPos(mScrollOffsetPrimary
+                        + getViewCenter(findViewByPosition(i)));
+                mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
+                break;
+            }
+        }
+        if (firstAvailable) {
             mWindowAlignment.mainAxis().setMinEdge(minEdge);
-            if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge);
+            mWindowAlignment.mainAxis().setMinScroll(minScroll);
+            if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
+                    " scrollMin to " + minScroll);
+        } else {
+            // the minScroll for currently first visible item is smaller,
+            // so we must invalidate the min scroll value.
+            if (minScroll < mWindowAlignment.mainAxis().getMinScroll()) {
+                mWindowAlignment.mainAxis().invalidateScrollMin();
+                if (DEBUG) Log.v(getTag(), "Invalidate scrollMin, since it should be "
+                        + "less than " + minScroll);
+            }
         }
     }
 
@@ -1735,8 +1772,6 @@
         mWindowAlignment.vertical.setSize(getHeight());
         mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
         mSizePrimary = mWindowAlignment.mainAxis().getSize();
-        mWindowAlignment.mainAxis().invalidateScrollMin();
-        mWindowAlignment.mainAxis().invalidateScrollMax();
 
         if (DEBUG) {
             Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
@@ -1760,11 +1795,10 @@
         if (mFocusPosition == position) {
             return;
         }
-        View view = getViewByPosition(position);
+        View view = findViewByPosition(position);
         if (view != null) {
             scrollToView(view, smooth);
         } else {
-            boolean right = position > mFocusPosition;
             mFocusPosition = position;
             if (!mLayoutEnabled) {
                 return;
@@ -1775,12 +1809,46 @@
                             "not be called before first layout pass");
                     return;
                 }
-                if (right) {
-                    appendVisibleItems();
-                } else {
-                    prependVisibleItems();
-                }
-                scrollToView(getViewByPosition(position), smooth);
+                LinearSmoothScroller linearSmoothScroller =
+                        new LinearSmoothScroller(parent.getContext()) {
+                    @Override
+                    public PointF computeScrollVectorForPosition(int targetPosition) {
+                        if (getChildCount() == 0) {
+                            return null;
+                        }
+                        final int firstChildPos = getPosition(getChildAt(0));
+                        final int direction = targetPosition < firstChildPos ? -1 : 1;
+                        if (mOrientation == HORIZONTAL) {
+                            return new PointF(direction, 0);
+                        } else {
+                            return new PointF(0, direction);
+                        }
+                    }
+                    @Override
+                    protected void onTargetFound(View targetView,
+                            RecyclerView.State state, Action action) {
+                        if (hasFocus()) {
+                            targetView.requestFocus();
+                        } else {
+                            dispatchChildSelected();
+                        }
+                        if (getScrollPosition(targetView, mTempDeltas)) {
+                            int dx, dy;
+                            if (mOrientation == HORIZONTAL) {
+                                dx = mTempDeltas[0];
+                                dy = mTempDeltas[1];
+                            } else {
+                                dx = mTempDeltas[1];
+                                dy = mTempDeltas[0];
+                            }
+                            final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+                            final int time = calculateTimeForDeceleration(distance);
+                            action.update(dx, dy, time, mDecelerateInterpolator);
+                        }
+                    }
+                };
+                linearSmoothScroller.setTargetPosition(position);
+                startSmoothScroll(linearSmoothScroller);
             } else {
                 mForceFullLayout = true;
                 parent.requestLayout();
@@ -1844,9 +1912,11 @@
      */
     private void scrollToView(View view, boolean smooth) {
         int newFocusPosition = getPositionByView(view);
-        if (mInLayout || newFocusPosition != mFocusPosition) {
+        if (newFocusPosition != mFocusPosition) {
             mFocusPosition = newFocusPosition;
-            dispatchChildSelected();
+            if (!mInLayout) {
+                dispatchChildSelected();
+            }
         }
         if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
             mBaseGridView.invalidate();
@@ -1859,19 +1929,23 @@
             // by setSelection())
             view.requestFocus();
         }
-        switch (mFocusScrollStrategy) {
-        case BaseGridView.FOCUS_SCROLL_ALIGNED:
-        default:
-            scrollToAlignedPosition(view, smooth);
-            break;
-        case BaseGridView.FOCUS_SCROLL_ITEM:
-        case BaseGridView.FOCUS_SCROLL_PAGE:
-            scrollItemOrPage(view, smooth);
-            break;
+        if (getScrollPosition(view, mTempDeltas)) {
+            scrollGrid(mTempDeltas[0], mTempDeltas[1], smooth);
         }
     }
 
-    private void scrollItemOrPage(View view, boolean smooth) {
+    private boolean getScrollPosition(View view, int[] deltas) {
+        switch (mFocusScrollStrategy) {
+        case BaseGridView.FOCUS_SCROLL_ALIGNED:
+        default:
+            return getAlignedPosition(view, deltas);
+        case BaseGridView.FOCUS_SCROLL_ITEM:
+        case BaseGridView.FOCUS_SCROLL_PAGE:
+            return getNoneAlignedPosition(view, deltas);
+        }
+    }
+
+    private boolean getNoneAlignedPosition(View view, int[] deltas) {
         int pos = getPositionByView(view);
         int viewMin = getViewMin(view);
         int viewMax = getViewMax(view);
@@ -1891,10 +1965,10 @@
                 while (!prependOneVisibleItem()) {
                     List<Integer> positions =
                             mGrid.getItemPositionsInRows(mFirstVisiblePos, pos)[row];
-                    firstView = getViewByPosition(positions.get(0));
+                    firstView = findViewByPosition(positions.get(0));
                     if (viewMax - getViewMin(firstView) > clientSize) {
                         if (positions.size() > 1) {
-                            firstView = getViewByPosition(positions.get(1));
+                            firstView = findViewByPosition(positions.get(1));
                         }
                         break;
                     }
@@ -1908,7 +1982,7 @@
                 do {
                     List<Integer> positions =
                             mGrid.getItemPositionsInRows(pos, mLastVisiblePos)[row];
-                    lastView = getViewByPosition(positions.get(positions.size() - 1));
+                    lastView = findViewByPosition(positions.get(positions.size() - 1));
                     if (getViewMax(lastView) - viewMin > clientSize) {
                         lastView = null;
                         break;
@@ -1939,36 +2013,34 @@
         }
         int viewCenterSecondary = mScrollOffsetSecondary +
                 getViewCenterSecondary(secondaryAlignedView);
-        mWindowAlignment.secondAxis().updateScrollCenter(viewCenterSecondary);
-        scrollSecondary = mWindowAlignment.secondAxis().getSystemScrollPos();
+        scrollSecondary = mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary);
         scrollSecondary -= mScrollOffsetSecondary;
-        scrollGrid(scrollPrimary, scrollSecondary, smooth);
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
+        }
+        return false;
     }
 
-    private void scrollToAlignedPosition(View view, boolean smooth) {
+    private boolean getAlignedPosition(View view, int[] deltas) {
         int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
         int viewCenterSecondary = mScrollOffsetSecondary + getViewCenterSecondary(view);
+
+        int scrollPrimary = mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary);
+        int scrollSecondary = mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary);
         if (DEBUG) {
-            Log.v(getTag(), "scrollAligned smooth=" + smooth + " pos=" + mFocusPosition + " "
-                    + viewCenterPrimary +","+viewCenterSecondary + " " + mWindowAlignment);
+            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
+                    +" " + mWindowAlignment);
         }
-
-        if (mInLayout || viewCenterPrimary != mWindowAlignment.mainAxis().getScrollCenter()
-                || viewCenterSecondary != mWindowAlignment.secondAxis().getScrollCenter()) {
-            mWindowAlignment.mainAxis().updateScrollCenter(viewCenterPrimary);
-            mWindowAlignment.secondAxis().updateScrollCenter(viewCenterSecondary);
-            int scrollPrimary = mWindowAlignment.mainAxis().getSystemScrollPos();
-            int scrollSecondary = mWindowAlignment.secondAxis().getSystemScrollPos();
-            if (DEBUG) {
-                Log.v(getTag(), "scrollAligned " + scrollPrimary + " " + scrollSecondary
-                        +" " + mWindowAlignment);
-            }
-
-            scrollPrimary -= mScrollOffsetPrimary;
-            scrollSecondary -= mScrollOffsetSecondary;
-
-            scrollGrid(scrollPrimary, scrollSecondary, smooth);
+        scrollPrimary -= mScrollOffsetPrimary;
+        scrollSecondary -= mScrollOffsetSecondary;
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
         }
+        return false;
     }
 
     private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
@@ -1995,35 +2067,6 @@
 
     public void setAnimateChildLayout(boolean animateChildLayout) {
         mAnimateChildLayout = animateChildLayout;
-        for (int i = 0, c = getChildCount(); i < c; i++) {
-            View v = getChildAt(i);
-            LayoutParams p = (LayoutParams) v.getLayoutParams();
-            if (!mAnimateChildLayout) {
-                p.endAnimate();
-            }
-        }
-    }
-
-    private void attemptRecordChildLayout() {
-        if (!mAnimateChildLayout) {
-            return;
-        }
-        for (int i = 0, c = getChildCount(); i < c; i++) {
-            View v = getChildAt(i);
-            ((LayoutParams) v.getLayoutParams()).recordStart(mOrientation, v);
-        }
-    }
-
-    private void attemptAnimateLayoutChild() {
-        if (!mAnimateChildLayout) {
-            return;
-        }
-        for (int i = 0, c = getChildCount(); i < c; i++) {
-            // TODO: start delay can be staggered
-            View v = getChildAt(i);
-            ((LayoutParams) v.getLayoutParams()).startAnimate(v,
-                    getChildLayoutAnimationDuration(), 0, getChildLayoutAnimationInterpolator());
-        }
     }
 
     public boolean isChildLayoutAnimated() {
@@ -2034,8 +2077,7 @@
         if (mPruneChild != pruneChild) {
             mPruneChild = pruneChild;
             if (mPruneChild) {
-                removeInvisibleViewsAtEnd();
-                removeInvisibleViewsAtFront();
+                requestLayout();
             }
         }
     }
@@ -2044,22 +2086,6 @@
         return mPruneChild;
     }
 
-    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
-        mAnimateLayoutChildInterpolator = interpolator;
-    }
-
-    public Interpolator getChildLayoutAnimationInterpolator() {
-        return mAnimateLayoutChildInterpolator;
-    }
-
-    public void setChildLayoutAnimationDuration(long duration) {
-        mAnimateLayoutChildDuration = duration;
-    }
-
-    public long getChildLayoutAnimationDuration() {
-        return mAnimateLayoutChildDuration;
-    }
-
     private int findImmediateChildIndex(View view) {
         while (view != null && view != mBaseGridView) {
             int index = mBaseGridView.indexOfChild(view);
@@ -2087,27 +2113,13 @@
         return null;
     }
 
-    boolean hasNextViewInSameRow(int pos) {
-        if (mGrid == null || pos == NO_POSITION) {
-            return false;
-        }
-        final int focusedRow = mGrid.getLocation(pos).row;
-        for (int i = 0, count = getChildCount(); i < count; i++) {
-            int position = getPositionByIndex(i);
-            StaggeredGrid.Location loc = mGrid.getLocation(position);
-            if (loc != null && loc.row == focusedRow) {
-                if (position > pos) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     boolean hasPreviousViewInSameRow(int pos) {
         if (mGrid == null || pos == NO_POSITION) {
             return false;
         }
+        if (mFirstVisiblePos > 0) {
+            return true;
+        }
         final int focusedRow = mGrid.getLocation(pos).row;
         for (int i = getChildCount() - 1; i >= 0; i--) {
             int position = getPositionByIndex(i);
@@ -2144,7 +2156,7 @@
             final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
             // Add focusables of focused item.
             if (focusedPos != NO_POSITION) {
-                getViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
+                findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
             }
             final int focusedRow = mGrid != null && focusedPos != NO_POSITION ?
                     mGrid.getLocation(focusedPos).row : NO_POSITION;
@@ -2209,10 +2221,11 @@
     }
 
     @Override
-    public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
-            Recycler recycler) {
+    public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
+            RecyclerView.State state) {
         if (DEBUG) Log.v(getTag(), "onFocusSearchFailed direction " + direction);
 
+        saveContext(recycler, state);
         View view = null;
         int movement = getMovement(direction);
         final FocusFinder ff = FocusFinder.getInstance();
@@ -2233,6 +2246,7 @@
                 view = mFocusOutEnd ? null : focused;
             }
         }
+        leaveContext();
         if (DEBUG) Log.v(getTag(), "returning view " + view);
         return view;
     }
@@ -2253,7 +2267,7 @@
 
     private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
             int direction, Rect previouslyFocusedRect) {
-        View view = getViewByPosition(mFocusPosition);
+        View view = findViewByPosition(mFocusPosition);
         if (view != null) {
             boolean result = view.requestFocus(direction, previouslyFocusedRect);
             if (!result && DEBUG) {
@@ -2339,10 +2353,11 @@
     }
 
     int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
-        int focusIndex = getIndexByPosition(mFocusPosition);
-        if (focusIndex == NO_POSITION) {
+        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) {
@@ -2358,6 +2373,7 @@
     public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
             RecyclerView.Adapter newAdapter) {
         discardLayoutInfo();
+        mFocusPosition = NO_POSITION;
         super.onAdapterChanged(oldAdapter, newAdapter);
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
index c4d6e4f..98acea6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
@@ -29,7 +29,6 @@
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.View;
-import android.view.ViewGroup;
 
 /**
  * A view that shows items in a horizontal scrolling list. The items come from
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
index bc4a476..e970171 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
@@ -14,7 +14,6 @@
 package android.support.v17.leanback.widget;
 
 import android.support.v7.widget.RecyclerView;
-import android.support.v17.leanback.R;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,7 +32,7 @@
      * Interface for listening to view holder operations.
      */
     public static class AdapterListener {
-        public void onAddPresenter(Presenter presenter) {
+        public void onAddPresenter(Presenter presenter, int type) {
         }
         public void onCreate(ViewHolder viewHolder) {
         }
@@ -198,6 +197,14 @@
         setAdapter(null);
     }
 
+    public void setPresenterMapper(ArrayList<Presenter> presenters) {
+        mPresenters = presenters;
+    }
+
+    public ArrayList<Presenter> getPresenterMapper() {
+        return mPresenters;
+    }
+
     @Override
     public int getItemCount() {
         return mAdapter.size();
@@ -213,8 +220,9 @@
         if (type < 0) {
             mPresenters.add(presenter);
             type = mPresenters.indexOf(presenter);
+            if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
             if (mAdapterListener != null) {
-                mAdapterListener.onAddPresenter(presenter);
+                mAdapterListener.onAddPresenter(presenter, type);
             }
         }
         return type;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index 93fdd9e..74ee0a2 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -13,20 +13,14 @@
  */
 package android.support.v17.leanback.widget;
 
-import java.util.ArrayList;
-
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
-import android.support.v17.leanback.widget.Presenter.ViewHolder;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
 
 /**
  * ListRowPresenter renders {@link ListRow} using a
@@ -82,6 +76,10 @@
         public final HorizontalGridView getGridView() {
             return mGridView;
         }
+
+        public final ItemBridgeAdapter getBridgeAdapter() {
+            return mItemBridgeAdapter;
+        }
     }
 
     private int mRowHeight;
@@ -91,6 +89,10 @@
     private boolean mShadowEnabled = true;
     private int mBrowseRowsFadingEdgeLength = -1;
 
+    private static int sSelectedRowTopPadding;
+    private static int sExpandedSelectedRowTopPadding;
+    private static int sExpandedRowNoHovercardBottomPadding;
+
     /**
      * Constructs a ListRowPresenter with defaults.
      * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming.
@@ -221,6 +223,11 @@
                 }
                 viewHolder.itemView.setActivated(rowViewHolder.mExpanded);
             }
+
+            @Override
+            public void onAddPresenter(Presenter presenter, int type) {
+                rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(type, 24);
+            }
         });
     }
 
@@ -269,8 +276,51 @@
         }
     }
 
+    private static void initStatics(Context context) {
+        if (sSelectedRowTopPadding == 0) {
+            sSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_selected_row_top_padding);
+            sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_expanded_selected_row_top_padding);
+            sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding);
+        }
+    }
+
+    private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) {
+        RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder();
+        if (headerViewHolder != null) {
+            if (getHeaderPresenter() != null) {
+                return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder);
+            }
+            return headerViewHolder.view.getPaddingBottom();
+        }
+        return 0;
+    }
+
+    private void setVerticalPadding(ListRowPresenter.ViewHolder vh) {
+        int paddingTop, paddingBottom;
+        if (vh.isExpanded()) {
+            int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh);
+            if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline);
+            paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) -
+                    headerSpaceUnderBaseline;
+            paddingBottom = mHoverCardPresenterSelector == null ?
+                    sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom;
+        } else if (vh.isSelected()) {
+            paddingTop = sSelectedRowTopPadding;
+            paddingBottom = sSelectedRowTopPadding - vh.mPaddingTop;
+        } else {
+            paddingTop = vh.mPaddingTop;
+            paddingBottom = 0;
+        }
+        vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight,
+                paddingBottom);
+    }
+
     @Override
     protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        initStatics(parent.getContext());
         ListRowView rowView = new ListRowView(parent.getContext());
         setupFadingEffect(rowView);
         if (mRowHeight != 0) {
@@ -282,7 +332,9 @@
     @Override
     protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
         super.onRowViewSelected(holder, selected);
-        updateFooterViewSwitcher((ViewHolder) holder);
+        ViewHolder vh = (ViewHolder) holder;
+        setVerticalPadding(vh);
+        updateFooterViewSwitcher(vh);
     }
 
     /*
@@ -326,13 +378,7 @@
             int newHeight = expanded ? getExpandedRowHeight() : getRowHeight();
             vh.getGridView().setRowHeight(newHeight);
         }
-        if (expanded) {
-            vh.getGridView().setPadding(vh.mPaddingLeft, vh.mPaddingTop,
-                    vh.mPaddingRight, vh.mPaddingBottom);
-        } else {
-            vh.getGridView().setPadding(vh.mPaddingLeft, vh.mPaddingTop,
-                    vh.mPaddingRight, 0);
-        }
+        setVerticalPadding(vh);
         vh.getGridView().setFadingLeftEdge(!expanded);
         updateFooterViewSwitcher(vh);
     }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
index 41da46f..ab5729b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
@@ -15,16 +15,10 @@
 
 import android.support.v17.leanback.R;
 import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.TypedValue;
 import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 /**
  * ListRowView contains a horizontal grid view.
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
index 531c1cf..5dd45e1 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
@@ -13,8 +13,6 @@
  */
 package android.support.v17.leanback.widget;
 
-import android.view.View;
-
 /**
  * Interface for receiving notification when an action is clicked.
  */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Row.java b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
index 893d5c0..acf76d4 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Row.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
@@ -16,7 +16,7 @@
 import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
 
 /**
- * A row in the RowContainerFragment.  This is the basic class for all Rows.
+ * A row in the RowsFragment.  This is the basic class for all Rows.
  * Developer usually overrides {@link ListRow}, but may override this class
  * for non-list Row (e.g. a HtmlRow).
  */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
index 64f46ad..886bf89 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -13,10 +13,12 @@
  */
 package android.support.v17.leanback.widget;
 
+import android.graphics.Paint;
 import android.support.v17.leanback.R;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 /**
  * RowHeaderPresenter provides a default implementation for header using TextView.
@@ -26,6 +28,7 @@
 public class RowHeaderPresenter extends Presenter {
 
     private final int mLayoutResourceId;
+    private final Paint mFontMeasurePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 
     public RowHeaderPresenter() {
         this(R.layout.lb_row_header);
@@ -78,7 +81,6 @@
 
     @Override
     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
-        ((RowHeaderView) viewHolder.view).setText(null);
     }
 
     public final void setSelectLevel(ViewHolder holder, float selectLevel) {
@@ -90,4 +92,26 @@
         holder.view.setAlpha(holder.mUnselectAlpha + holder.mSelectLevel *
                 (1f - holder.mUnselectAlpha));
     }
-}
\ No newline at end of file
+
+    /**
+     * Returns the space (distance in pixels) below the baseline of the
+     * text view, if one exists; otherwise, returns 0.
+     */
+    public int getSpaceUnderBaseline(ViewHolder holder) {
+        int space = holder.view.getPaddingBottom();
+        if (holder.view instanceof TextView) {
+            space += (int) getFontDescent((TextView) holder.view, mFontMeasurePaint);
+        }
+        return space;
+    }
+
+    protected static float getFontDescent(TextView textView, Paint fontMeasurePaint) {
+        if (fontMeasurePaint.getTextSize() != textView.getTextSize()) {
+            fontMeasurePaint.setTextSize(textView.getTextSize());
+        }
+        if (fontMeasurePaint.getTypeface() != textView.getTypeface()) {
+            fontMeasurePaint.setTypeface(textView.getTypeface());
+        }
+        return fontMeasurePaint.descent();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
index acd6ff3..71018ad 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -15,9 +15,12 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.SoundPool;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -29,9 +32,10 @@
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.TypedValue;
+import android.util.SparseIntArray;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -43,17 +47,24 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
- * SearchBar is a search widget.
+ * <p>SearchBar is a search widget.</p>
+ *
+ * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
  */
 public class SearchBar extends RelativeLayout {
     private static final String TAG = SearchBar.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private SpeechRecognizer mSpeechRecognizer;
-    private boolean mListening;
+    private static final float FULL_LEFT_VOLUME = 1.0f;
+    private static final float FULL_RIGHT_VOLUME = 1.0f;
+    private static final int DEFAULT_PRIORITY = 1;
+    private static final int DO_NOT_LOOP = 0;
+    private static final float DEFAULT_RATE = 1.0f;
 
     /**
      * Listener for search query changes
@@ -68,7 +79,10 @@
         public void onSearchQueryChange(String query);
 
         /**
-         * Method invoked when the search query is submitted.
+         * <p>Method invoked when the search query is submitted.</p>
+         *
+         * <p>This method can be called without a preceeding onSearchQueryChange,
+         * in particular in the case of a voice input.</p>
          *
          * @param query The query being submitted.
          */
@@ -99,6 +113,10 @@
     private int mBackgroundAlpha;
     private int mBackgroundSpeechAlpha;
     private int mBarHeight;
+    private SpeechRecognizer mSpeechRecognizer;
+    private boolean mListening;
+    private SoundPool mSoundPool;
+    private SparseIntArray mSoundMap = new SparseIntArray();
 
     public SearchBar(Context context) {
         this(context, null);
@@ -110,10 +128,14 @@
 
     public SearchBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        enforceAudioRecordPermission();
+
+        Resources r = getResources();
 
         LayoutInflater inflater = LayoutInflater.from(getContext());
         inflater.inflate(R.layout.lb_search_bar, this, true);
 
+        mBarHeight = getResources().getDimensionPixelSize(R.dimen.lb_search_bar_height);
         RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                 mBarHeight);
         params.addRule(ALIGN_PARENT_TOP, RelativeLayout.TRUE);
@@ -124,16 +146,15 @@
         mSearchQuery = "";
         mInputMethodManager =
                 (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
-        mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
 
-        Resources r = getResources();
         mTextSpeechColor = r.getColor(R.color.lb_search_bar_text_speech_color);
         mBackgroundSpeechAlpha = r.getInteger(R.integer.lb_search_bar_speech_mode_background_alpha);
 
         mTextColor = r.getColor(R.color.lb_search_bar_text_color);
         mBackgroundAlpha = r.getInteger(R.integer.lb_search_bar_text_mode_background_alpha);
 
-        mBarHeight = getResources().getDimensionPixelSize(R.dimen.lb_search_bar_height);
+        mSoundPool = new SoundPool(8, AudioManager.STREAM_SYSTEM, 0);
+        loadSounds(context);
     }
 
     @Override
@@ -342,12 +363,46 @@
         return mBadgeDrawable;
     }
 
-    protected void hideNativeKeyboard() {
+    /**
+     * Update the completion list shown by the IME
+     *
+     * @param completions list of completions shown in the IME, can be null or empty to clear them
+     */
+    public void displayCompletions(List<String> completions) {
+        List<CompletionInfo> infos = new ArrayList<CompletionInfo>();
+        if (null != completions) {
+            for (String completion : completions) {
+                infos.add(new CompletionInfo(infos.size(), infos.size(), completion));
+            }
+        }
+
+        mInputMethodManager.displayCompletions(mSearchTextEditor,
+                infos.toArray(new CompletionInfo[] {}));
+    }
+
+    /**
+     * Set the speech recognizer to be used when doing voice search. The Activity/Fragment is in
+     * charge of creating and destroying the recognizer with its own lifecycle.
+     *
+     * @param recognizer a SpeechRecognizer
+     */
+    public void setSpeechRecognizer(SpeechRecognizer recognizer) {
+        if (null != mSpeechRecognizer) {
+            mSpeechRecognizer.setRecognitionListener(null);
+            if (mListening) {
+                mSpeechRecognizer.stopListening();
+                mListening = false;
+            }
+        }
+        mSpeechRecognizer = recognizer;
+    }
+
+    private void hideNativeKeyboard() {
         mInputMethodManager.hideSoftInputFromWindow(mSearchTextEditor.getWindowToken(),
                 InputMethodManager.RESULT_UNCHANGED_SHOWN);
     }
 
-    protected void showNativeKeyboard() {
+    private void showNativeKeyboard() {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -365,7 +420,7 @@
     /**
      * This will update the hint for the search bar properly depending on state and provided title
      */
-    protected void updateHint() {
+    private void updateHint() {
         if (null == mSearchTextEditor) return;
 
         String title = getResources().getString(R.string.lb_search_bar_hint);
@@ -381,7 +436,9 @@
         mSearchTextEditor.setHint(title);
     }
 
-    protected void stopRecognition() {
+    private void stopRecognition() {
+        if (null == mSpeechRecognizer) return;
+
         if (DEBUG) Log.v(TAG, "stopRecognition " + mListening);
         mSpeechOrbView.showNotListening();
 
@@ -390,7 +447,9 @@
         }
     }
 
-    protected void startRecognition() {
+    private void startRecognition() {
+        if (null == mSpeechRecognizer) return;
+
         if (DEBUG) Log.v(TAG, "startRecognition " + mListening);
 
         mSearchTextEditor.setText("");
@@ -456,6 +515,7 @@
                 mListening = false;
                 mSpeechRecognizer.setRecognitionListener(null);
                 mSpeechOrbView.showNotListening();
+                playSearchFailure();
             }
 
             @Override
@@ -476,6 +536,7 @@
                 }
                 mSpeechRecognizer.setRecognitionListener(null);
                 mSpeechOrbView.showNotListening();
+                playSearchSuccess();
             }
 
             @Override
@@ -490,11 +551,12 @@
         });
 
         mSpeechOrbView.showListening();
+        playSearchOpen();
         mSpeechRecognizer.startListening(recognizerIntent);
         mListening = true;
     }
 
-    protected void updateUi() {
+    private void updateUi() {
         if (DEBUG) Log.v(TAG, String.format("Update UI %s %s",
                 isVoiceMode() ? "Voice" : "Text",
                 hasFocus() ? "Focused" : "Unfocused"));
@@ -509,15 +571,62 @@
         updateHint();
     }
 
-    protected boolean isVoiceMode() {
+    private boolean isVoiceMode() {
         return mSpeechOrbView.isFocused();
     }
 
-    protected void submitQuery() {
+    private void submitQuery() {
         if (!TextUtils.isEmpty(mSearchQuery) && null != mSearchBarListener) {
             mSearchBarListener.onSearchQuerySubmit(mSearchQuery);
         }
     }
 
+    private void enforceAudioRecordPermission() {
+        String permission = "android.permission.RECORD_AUDIO";
+        int res = getContext().checkCallingOrSelfPermission(permission);
+        if (PackageManager.PERMISSION_GRANTED != res) {
+            throw new IllegalStateException("android.premission.RECORD_AUDIO required for search");
+        }
+    }
+
+    private void loadSounds(Context context) {
+        int[] sounds = {
+                R.raw.lb_voice_failure,
+                R.raw.lb_voice_open,
+                R.raw.lb_voice_no_input,
+                R.raw.lb_voice_success,
+        };
+        for (int sound : sounds) {
+            mSoundMap.put(sound, mSoundPool.load(context, sound, 1));
+        }
+    }
+
+    private void play(final int resId) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                int sound = mSoundMap.get(resId);
+                mSoundPool.play(sound, FULL_LEFT_VOLUME, FULL_RIGHT_VOLUME, DEFAULT_PRIORITY,
+                        DO_NOT_LOOP, DEFAULT_RATE);
+            }
+        });
+    }
+
+    private void playSearchOpen() {
+        play(R.raw.lb_voice_open);
+    }
+
+    private void playSearchFailure() {
+        play(R.raw.lb_voice_failure);
+    }
+
+    private void playSearchNoInput() {
+        play(R.raw.lb_voice_no_input);
+    }
+
+    private void playSearchSuccess() {
+        play(R.raw.lb_voice_success);
+    }
+
 
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
index f64946b..901b7f8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
@@ -16,45 +16,96 @@
 
 package android.support.v17.leanback.widget;
 
-
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.support.v17.leanback.R;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 
+/**
+ * <p>A widget that draws a search affordance, represented by a round background and an icon.</p>
+ *
+ * Background color and icon can be customized
+ */
 public class SearchOrbView extends FrameLayout implements View.OnClickListener {
     private OnClickListener mListener;
     private View mSearchOrbView;
+    private ImageView mIcon;
+    private Drawable mIconDrawable;
+    private int mSearchOrbColor, mSearchOrbColorBright;
     private final float mFocusedZoom;
+    private final float mBrightnessAlpha;
+    private final int mPulseDurationMs;
+    private final int mScaleDownDurationMs;
+    private ValueAnimator mColorAnimator;
+
+    private final ArgbEvaluator mColorEvaluator = new ArgbEvaluator();
+
+    private final ValueAnimator.AnimatorUpdateListener mUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animator) {
+            Integer color = (Integer) animator.getAnimatedValue();
+            setOrbViewColor(color.intValue());
+        }
+    };
 
     public SearchOrbView(Context context) {
         this(context, null);
     }
 
     public SearchOrbView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
+        this(context, attrs, R.attr.searchOrbViewStyle);
     }
 
-    public SearchOrbView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public SearchOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final Resources res = context.getResources();
 
         LayoutInflater inflater = (LayoutInflater) context
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View root = inflater.inflate(R.layout.lb_search_orb, this, true);
         mSearchOrbView = root.findViewById(R.id.search_orb);
-
-        // By default we are not visible
-        setVisibility(INVISIBLE);
-        setFocusable(true);
-        setClipChildren(false);
+        mIcon = (ImageView)root.findViewById(R.id.icon);
 
         mFocusedZoom = context.getResources().getFraction(
                 R.fraction.lb_search_orb_focused_zoom, 1, 1);
+        mBrightnessAlpha = context.getResources().getFraction(
+                R.fraction.lb_search_orb_brightness_alpha, 1, 1);
+        mPulseDurationMs = context.getResources().getInteger(
+                R.integer.lb_search_orb_pulse_duration_ms);
+        mScaleDownDurationMs = context.getResources().getInteger(
+                R.integer.lb_search_orb_scale_down_duration_ms);
 
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSearchOrbView,
+                defStyleAttr, 0);
+
+        Drawable img = a.getDrawable(R.styleable.lbSearchOrbView_searchOrbIcon);
+        if (img == null) {
+            img = res.getDrawable(R.drawable.lb_ic_in_app_search);
+        }
+        setOrbIcon(img);
+
+        int defColor = res.getColor(R.color.lb_default_search_color);
+        int color = a.getColor(R.styleable.lbSearchOrbView_searchOrbColor, defColor);
+        int brightColor = a.getColor(
+                R.styleable.lbSearchOrbView_searchOrbBrightColor, getBrightColor(color));
+        setOrbColor(color, brightColor);
+        a.recycle();
+
+        setFocusable(true);
+        setClipChildren(false);
         setOnClickListener(this);
     }
 
@@ -69,7 +120,26 @@
     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
         final float zoom = gainFocus ? mFocusedZoom : 1f;
-        mSearchOrbView.animate().scaleX(zoom).scaleY(zoom).setDuration(200).start();
+        final int duration = gainFocus ? mPulseDurationMs : mScaleDownDurationMs;
+        mSearchOrbView.animate().scaleX(zoom).scaleY(zoom).setDuration(duration).start();
+        enableOrbColorAnimation(gainFocus);
+    }
+
+    /**
+     * Set the orb icon
+     * @param icon the drawable to be used as the icon
+     */
+    public void setOrbIcon(Drawable icon) {
+        mIconDrawable = icon;
+        mIcon.setImageDrawable(mIconDrawable);
+    }
+
+    /**
+     * Returns the orb icon
+     * @return the drawable used as the icon
+     */
+    public Drawable getOrbIcon() {
+        return mIconDrawable;
     }
 
     /**
@@ -85,9 +155,62 @@
         }
     }
 
+    /**
+     * Set the background color of the search orb.
+     * @param color the RGBA color
+     */
     public void setOrbColor(int color) {
+        setOrbColor(color, getBrightColor(color));
+    }
+
+    public void setOrbColor(int color, int brightColor) {
+        mSearchOrbColor = color;
+        mSearchOrbColorBright = brightColor;
+
+        if (mColorAnimator == null) {
+            setOrbViewColor(color);
+        } else {
+            enableOrbColorAnimation(true);
+        }
+    }
+
+    /**
+     * Returns the orb color
+     * @return the RGBA color
+     */
+    public int getOrbColor() {
+        return mSearchOrbColor;
+    }
+
+    private int getBrightColor(int color) {
+        final float brightnessValue = 0xff * mBrightnessAlpha;
+        int red = (int)(Color.red(color) * (1 - mBrightnessAlpha) + brightnessValue);
+        int green = (int)(Color.green(color) * (1 - mBrightnessAlpha) + brightnessValue);
+        int blue = (int)(Color.blue(color) * (1 - mBrightnessAlpha) + brightnessValue);
+        int alpha = (int)(Color.alpha(color) * (1 - mBrightnessAlpha) + brightnessValue);
+        return Color.argb(alpha, red, green, blue);
+    }
+
+    private void enableOrbColorAnimation(boolean enable) {
+        if (mColorAnimator != null) {
+            mColorAnimator.end();
+            mColorAnimator = null;
+        }
+        if (enable) {
+            // TODO: set interpolator (material if available)
+            mColorAnimator = ValueAnimator.ofObject(mColorEvaluator,
+                    mSearchOrbColor, mSearchOrbColorBright, mSearchOrbColor);
+            mColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
+            mColorAnimator.setDuration(mPulseDurationMs * 2);
+            mColorAnimator.addUpdateListener(mUpdateListener);
+            mColorAnimator.start();
+        }
+    }
+
+    private void setOrbViewColor(int color) {
         if (mSearchOrbView.getBackground() instanceof GradientDrawable) {
             ((GradientDrawable) mSearchOrbView.getBackground()).setColor(color);
         }
     }
+
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
index 00ed8d4..7e711ff 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -13,7 +13,6 @@
  */
 package android.support.v17.leanback.widget;
 
-import android.content.Context;
 import android.os.Build;
 import android.view.ViewGroup;
 
@@ -86,10 +85,36 @@
     }
 
     /**
+     * Implementation used on api 21 (and above).
+     */
+    private static final class ShadowHelperApi21Impl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            // do nothing
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            return ShadowHelperApi21.addShadow(shadowContainer);
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            ShadowHelperApi21.setShadowFocusLevel(impl, level);
+        }
+
+    }
+
+    /**
      * Returns the ShadowHelper.
      */
     private ShadowHelper() {
-        if (Build.VERSION.SDK_INT >= 18) {
+     // TODO: we should use version number once "L" is published
+        if ("L".equals(Build.VERSION.RELEASE)) {
+            mSupportsShadow = true;
+            mImpl = new ShadowHelperApi21Impl();
+        } else if (Build.VERSION.SDK_INT >= 18) {
             mSupportsShadow = true;
             mImpl = new ShadowHelperJbmr2Impl();
         } else {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
index 62bc191..4ba0cc6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
@@ -19,6 +19,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.graphics.Rect;
 
 /**
  * ShadowOverlayContainer Provides a SDK version independent wrapper container
@@ -49,6 +50,7 @@
     private View mColorDimOverlay;
     private Object mShadowImpl;
     private View mWrappedView;
+    private static final Rect sTempRect = new Rect();
 
     public ShadowOverlayContainer(Context context) {
         this(context, null, 0);
@@ -195,6 +197,13 @@
                 child.layout(0, 0, width, height);
             }
         }
+        if (mWrappedView != null) {
+            sTempRect.left = (int) mWrappedView.getPivotX();
+            sTempRect.top = (int) mWrappedView.getPivotY();
+            offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
+            setPivotX(sTempRect.left);
+            setPivotY(sTempRect.top);
+        }
     }
 
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
index b4de4a6..5404418 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -234,12 +234,12 @@
     }
 
     protected final Location appendItemToRow(int itemIndex, int rowIndex) {
-        mProvider.createItem(itemIndex, rowIndex, true);
         Location loc = new Location(rowIndex);
         if (mLocations.size() == 0) {
             mFirstIndex = itemIndex;
         }
         mLocations.addLast(loc);
+        mProvider.createItem(itemIndex, rowIndex, true);
         return loc;
     }
 
@@ -269,10 +269,10 @@
     }
 
     protected final Location prependItemToRow(int itemIndex, int rowIndex) {
-        mProvider.createItem(itemIndex, rowIndex, false);
         Location loc = new Location(rowIndex);
         mFirstIndex = itemIndex;
         mLocations.addFirst(loc);
+        mProvider.createItem(itemIndex, rowIndex, false);
         return loc;
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
new file mode 100644
index 0000000..c5a714a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
@@ -0,0 +1,132 @@
+/*
+ * 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.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Title view for a leanback fragment.
+ * @hide
+ */
+public class TitleView extends FrameLayout {
+
+    private ImageView mBadgeView;
+    private TextView mTextView;
+    private SearchOrbView mSearchOrbView;
+
+    public TitleView(Context context) {
+        this(context, null);
+    }
+
+    public TitleView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.browseTitleViewStyle);
+    }
+
+    public TitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View rootView = inflater.inflate(R.layout.lb_title_view, this);
+
+        mBadgeView = (ImageView) rootView.findViewById(R.id.browse_badge);
+        mTextView = (TextView) rootView.findViewById(R.id.browse_title);
+        mSearchOrbView = (SearchOrbView) rootView.findViewById(R.id.browse_orb);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbTitleView,
+                defStyleAttr, 0);
+
+        int defColor = context.getResources().getColor(R.color.lb_default_search_color);
+        int color = a.getColor(R.styleable.lbTitleView_searchAffordanceColor, defColor);
+        a.recycle();
+        mSearchOrbView.setOrbColor(color);
+
+        setClipToPadding(false);
+        setClipChildren(false);
+    }
+
+    /**
+     * Sets the title text.
+     */
+    public void setTitle(String titleText) {
+        mTextView.setText(titleText);
+    }
+
+    /**
+     * Returns the title text.
+     */
+    public CharSequence getTitle() {
+        return mTextView.getText();
+    }
+
+    /**
+     * Sets the badge drawable.
+     * If non-null, the drawable is displayed instead of the title text.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeView.setImageDrawable(drawable);
+        if (drawable != null) {
+            mBadgeView.setVisibility(View.VISIBLE);
+            mTextView.setVisibility(View.GONE);
+        } else {
+            mBadgeView.setVisibility(View.GONE);
+            mTextView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Returns the badge drawable.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeView.getDrawable();
+    }
+
+    /**
+     * Sets the listener to be called when the search affordance is clicked.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mSearchOrbView.setOnOrbClickedListener(listener);
+    }
+
+    /**
+     *  Returns the view for the search affordance.
+     */
+    public View getSearchAffordanceView() {
+        return mSearchOrbView;
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        mSearchOrbView.setOrbColor(color);
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return mSearchOrbView.getOrbColor();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
index 586ebf9..c68c59d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
@@ -182,6 +182,11 @@
                     });
                 }
             }
+
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                viewHolder.itemView.setActivated(true);
+            }
         });
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
index 5b10d41..e349354 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
@@ -19,7 +19,6 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.ViewGroup;
 
 /**
  * A view that shows items in a vertically scrolling list. The items come from
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index 7d79fc5..e8073e8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -44,6 +44,14 @@
          * Left or top edge of first child, typically should be zero.
          */
         private int mMinEdge;
+        /**
+         * Max Scroll value
+         */
+        private int mMaxScroll;
+        /**
+         * Min Scroll value
+         */
+        private int mMinScroll;
 
         private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
 
@@ -101,8 +109,22 @@
             mMinEdge = minEdge;
         }
 
-        public void invalidateScrollMin() {
+        final public int getMinEdge() {
+            return mMinEdge;
+        }
+
+        /** set minScroll,  Integer.MIN_VALUE means unknown*/
+        final public void setMinScroll(int minScroll) {
+            mMinScroll = minScroll;
+        }
+
+        final public int getMinScroll() {
+            return mMinScroll;
+        }
+
+        final public void invalidateScrollMin() {
             mMinEdge = Integer.MIN_VALUE;
+            mMinScroll = Integer.MIN_VALUE;
         }
 
         /** update max edge,  Integer.MAX_VALUE means unknown*/
@@ -110,8 +132,22 @@
             mMaxEdge = maxEdge;
         }
 
-        public void invalidateScrollMax() {
+        final public int getMaxEdge() {
+            return mMaxEdge;
+        }
+
+        /** update max scroll,  Integer.MAX_VALUE means unknown*/
+        final public void setMaxScroll(int maxScroll) {
+            mMaxScroll = maxScroll;
+        }
+
+        final public int getMaxScroll() {
+            return mMaxScroll;
+        }
+
+        final public void invalidateScrollMax() {
             mMaxEdge = Integer.MAX_VALUE;
+            mMaxScroll = Integer.MAX_VALUE;
         }
 
         final public float updateScrollCenter(float scrollTarget) {
diff --git a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
new file mode 100644
index 0000000..645b633
--- /dev/null
+++ b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
@@ -0,0 +1,27 @@
+/*
+ * 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.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+
+class ResourcesCompatApi21 {
+    public static Drawable getDrawable(Resources res, int id, Theme theme) {
+        return res.getDrawable(id, theme);
+    }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index 52f5b6e..59790bd 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,6 +1,11 @@
 apply plugin: 'android-library'
 archivesBaseName = 'support-v4'
 
+// create a jar task for the code internal implementation
+tasks.create(name: "internalJar", type: Jar) {
+    baseName "internal_impl"
+}
+
 // --------------------------
 // TO ADD NEW PLATFORM SPECIFIC CODE, UPDATE THIS:
 // create and configure the sourcesets/dependencies for platform-specific code.
@@ -36,6 +41,9 @@
         setupDependencies(configName, previousSource)
     }
     ext.allSS.add(sourceSet)
+
+    internalJar.from sourceSet.output
+
     return sourceSet
 }
 
@@ -44,15 +52,6 @@
     project.getDependencies().add(configName, previousSourceSet.compileClasspath)
 }
 
-// create a jar task for the code above
-tasks.create(name: "internalJar", type: Jar) {
-    baseName "internal_impl"
-}
-
-ext.allSS.each { ss ->
-    internalJar.from ss.output
-}
-
 dependencies {
     compile project(':support-annotations')
 
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index 516511a..c4e00f8 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -1703,10 +1703,9 @@
          * <pre class="prettyprint">
          * NotificationCompat.Action action = new NotificationCompat.Action.Builder(
          *         R.drawable.archive_all, "Archive all", actionIntent)
-         *         .apply(new NotificationCompat.Action.WearableExtender()
+         *         .extend(new NotificationCompat.Action.WearableExtender()
          *                 .setAvailableOffline(false))
-         *         .build();
-         * </pre>
+         *         .build();</pre>
          */
         public static final class WearableExtender implements Extender {
             /** Notification action extra which contains wearable extensions */
@@ -1863,8 +1862,7 @@
      * <pre class="prettyprint">
      * NotificationCompat.WearableExtender wearableExtender =
      *         new NotificationCompat.WearableExtender(notification);
-     * List&lt;Notification&gt; pages = wearableExtender.getPages();
-     * </pre>
+     * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
      */
     public static final class WearableExtender implements Extender {
         /**
@@ -1913,6 +1911,14 @@
          */
         public static final int SIZE_LARGE = 4;
 
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * full screen.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_FULL_SCREEN = 5;
+
         /** Notification extra which contains wearable extensions */
         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
 
@@ -2114,7 +2120,27 @@
 
         /**
          * Set an intent to launch inside of an activity view when displaying
-         * this notification. This {@link PendingIntent} should be for an activity.
+         * this notification. The {@link PendingIntent} provided should be for an activity.
+         *
+         * <pre class="prettyprint">
+         * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
+         * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
+         *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+         * Notification notif = new NotificationCompat.Builder(context)
+         *         .extend(new NotificationCompat.WearableExtender()
+         *                 .setDisplayIntent(displayPendingIntent)
+         *                 .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM))
+         *         .build();</pre>
+         *
+         * <p>The activity to launch needs to allow embedding, must be exported, and
+         * should have an empty task affinity.
+         *
+         * <p>Example AndroidManifest.xml entry:
+         * <pre class="prettyprint">
+         * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
+         *     android:exported=&quot;true&quot;
+         *     android:allowEmbedded=&quot;true&quot;
+         *     android:taskAffinity=&quot;&quot; /&gt;</pre>
          *
          * @param intent the {@link PendingIntent} for an activity
          * @return this object for method chaining
@@ -2248,12 +2274,17 @@
 
         /**
          * Set an action from this notification's actions to be clickable with the content of
-         * this notification page. This action will no longer display separately from the
-         * notification content. This action's icon will display with optional subtext provided
-         * by the action's title.
-         * @param actionIndex The index of the action to hoist on the current notification page.
-         *                    If wearable actions are present, this index will apply to that list,
-         *                    otherwise it will apply to the main notification's actions list.
+         * this notification. This action will no longer display separately from the
+         * notification's content.
+         *
+         * <p>For notifications with multiple pages, child pages can also have content actions
+         * set, although the list of available actions comes from the main notification and not
+         * from the child page's notification.
+         *
+         * @param actionIndex The index of the action to hoist onto the current notification page.
+         *                    If wearable actions were added to the main notification, this index
+         *                    will apply to that list, otherwise it will apply to the regular
+         *                    actions list.
          */
         public WearableExtender setContentAction(int actionIndex) {
             mContentActionIndex = actionIndex;
@@ -2261,14 +2292,18 @@
         }
 
         /**
-         * Get the action index of an action from this notification to show as clickable with
-         * the content of this notification page. When the user clicks this notification page,
-         * this action will trigger. This action will no longer display separately from the
-         * notification content. The action's icon will display with optional subtext provided
-         * by the action's title.
+         * Get the index of the notification action, if any, that was specified as being clickable
+         * with the content of this notification. This action will no longer display separately
+         * from the notification's content.
          *
-         * <p>If wearable specific actions are present, this index will apply to that list,
-         * otherwise it will apply to the main notification's actions list.
+         * <p>For notifications with multiple pages, child pages can also have content actions
+         * set, although the list of available actions comes from the main notification and not
+         * from the child page's notification.
+         *
+         * <p>If wearable specific actions were added to the main notification, this index will
+         * apply to that list, otherwise it will apply to the regular actions list.
+         *
+         * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
          */
         public int getContentAction() {
             return mContentActionIndex;
diff --git a/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java b/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java
index 5da2182..2917e11 100644
--- a/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java
+++ b/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java
@@ -27,7 +27,7 @@
  * {@link android.support.v4.app.NotificationManagerCompat}.
  *
  * <p>To receive side channel notifications, extend this service and register it in your
- * android manifest with an intent filter for the BIND_SIDE_CHANNEL action.
+ * android manifest with an intent filter for the BIND_NOTIFICATION_SIDE_CHANNEL action.
  * Note: you must also have an enabled
  * {@link android.service.notification.NotificationListenerService} within your package.
  *
@@ -50,17 +50,17 @@
     }
 
     /**
-     * Handle a side-channel notification was posted to the service.
+     * Handle a side-channeled notification being posted.
      */
     public abstract void notify(String packageName, int id, String tag, Notification notification);
 
     /**
-     * Handle a side-channel cancelling of an already-notified notification.
+     * Handle a side-channelled notification being cancelled.
      */
     public abstract void cancel(String packageName, int id, String tag);
 
     /**
-     * Handle a side-channel cancelling of all notifications for the given package.
+     * Handle the side-channelled cancelling of all notifications for a package.
      */
     public abstract void cancelAll(String packageName);
 
diff --git a/v4/java/android/support/v4/content/res/ResourcesCompat.java b/v4/java/android/support/v4/content/res/ResourcesCompat.java
new file mode 100644
index 0000000..2dbd334
--- /dev/null
+++ b/v4/java/android/support/v4/content/res/ResourcesCompat.java
@@ -0,0 +1,56 @@
+/*
+ * 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.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+/**
+ * Helper for accessing features in {@link android.content.res.Resources}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class ResourcesCompat {
+    /**
+     * Return a drawable object associated with a particular resource ID and
+     * styled for the specified theme. Various types of objects will be
+     * returned depending on the underlying resource -- for example, a solid
+     * color, PNG image, scalable image, etc.
+     * <p>
+     * Prior to API level 21, the theme will not be applied and this method
+     * simply calls through to {@link Resources#getDrawable(int)}.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param theme The theme used to style the drawable attributes, may be {@code null}.
+     * @return Drawable An object that can be used to draw this resource.
+     * @throws NotFoundException Throws NotFoundException if the given ID does
+     *         not exist.
+     */
+    public Drawable getDrawable(Resources res, int id, Theme theme)
+            throws NotFoundException {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ResourcesCompatApi21.getDrawable(res, id, theme);
+        } else {
+            return res.getDrawable(id);
+        }
+    }
+}
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index ddd9d59..0539a1a 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -16,6 +16,20 @@
 
 <resources>
 
+    <!--
+        Theme in the "Theme.Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version. They should not set any styleable attributes. Instead
+        you should create a "Theme.Base" theme which inherits from a "Theme.Platform" theme.
+    -->
+    <eat-comment/>
+    <style name="Theme.Platform.AppCompat" parent="android:Theme.Holo" />
+
+    <style name="Theme.Platform.AppCompat.Light" parent="android:Theme.Holo.Light" />
+
+    <style name="Theme.Platform.AppCompat.Dialog" parent="android:Theme.Holo.Dialog" />
+
+    <style name="Theme.Platform.AppCompat.Light.Dialog" parent="android:Theme.Holo.Light.Dialog" />
+
     <!-- Themes in the "Theme.Base" family vary based on the current platform
           version to provide the correct basis on each device. You probably don't
           want to use them directly in your apps.
@@ -28,7 +42,7 @@
     <eat-comment/>
 
     <!-- Base platform-dependent theme  -->
-    <style name="Theme.Base" parent="android:Theme.Holo">
+    <style name="Theme.Base" parent="Theme.Platform.AppCompat">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
@@ -46,7 +60,7 @@
     </style>
 
     <!-- Base platform-dependent theme providing a light-themed activity. -->
-    <style name="Theme.Base.Light" parent="android:Theme.Holo.Light">
+    <style name="Theme.Base.Light" parent="Theme.Platform.AppCompat.Light">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
@@ -63,7 +77,7 @@
         <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
     </style>
 
-    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="android:Theme.Holo.Dialog">
+    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="Theme.Platform.AppCompat.Dialog">
         <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
         <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
         <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
@@ -77,7 +91,7 @@
     </style>
 
     <style name="Theme.Base.AppCompat.Dialog.Light.FixedSize"
-           parent="android:Theme.Holo.Light.Dialog">
+           parent="Theme.Platform.AppCompat.Light.Dialog">
         <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
         <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
         <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
diff --git a/v7/appcompat/res/values-v14/themes_base.xml b/v7/appcompat/res/values-v14/themes_base.xml
index 3b30074..e182b2e 100644
--- a/v7/appcompat/res/values-v14/themes_base.xml
+++ b/v7/appcompat/res/values-v14/themes_base.xml
@@ -16,6 +16,23 @@
 
 <resources>
 
+    <!--
+        Theme in the "Theme.Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version. They should not set any styleable attributes. Instead
+        you should create a "Theme.Base" theme which inherits from a "Theme.Platform" theme.
+    -->
+    <eat-comment/>
+
+    <style name="Theme.Platform.AppCompat.Light.DarkActionBar"
+           parent="android:Theme.Holo.Light.DarkActionBar" />
+
+    <style name="Theme.Platform.AppCompat.DialogWhenLarge"
+           parent="android:Theme.Holo.DialogWhenLarge" />
+
+    <style name="Theme.Platform.AppCompat.Light.DialogWhenLarge"
+           parent="android:Theme.Holo.Light.DialogWhenLarge" />
+
+
     <!-- Themes in the "Theme.Base" family vary based on the current platform
           version to provide the correct basis on each device. You probably don't
           want to use them directly in your apps.
@@ -28,7 +45,7 @@
     <eat-comment/>
 
     <!-- Base platform-dependent theme providing an action bar in a dark-themed activity. -->
-    <style name="Theme.Base.AppCompat" parent="android:Theme.Holo">
+    <style name="Theme.Base.AppCompat" parent="Theme.Platform.AppCompat">
         <!-- Copy system flag values for our use -->
         <item name="windowActionBar">?android:attr/windowActionBar</item>
         <item name="actionBarSize">?android:attr/actionBarSize</item>
@@ -58,7 +75,7 @@
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
-    <style name="Theme.Base.AppCompat.Light" parent="android:Theme.Holo.Light">
+    <style name="Theme.Base.AppCompat.Light" parent="Theme.Platform.AppCompat.Light">
         <!-- Copy system flag values for our use -->
         <item name="windowActionBar">?android:attr/windowActionBar</item>
         <item name="actionBarSize">?android:attr/actionBarSize</item>
@@ -89,7 +106,7 @@
 
     <!-- Base platform-dependent theme providing a dark action bar in a light-themed activity. -->
     <style name="Theme.Base.AppCompat.Light.DarkActionBar"
-           parent="android:Theme.Holo.Light.DarkActionBar">
+           parent="Theme.Platform.AppCompat.Light.DarkActionBar">
         <!-- Copy system flag values for our use -->
         <item name="windowActionBar">?android:attr/windowActionBar</item>
         <item name="actionBarSize">?android:attr/actionBarSize</item>
@@ -132,7 +149,7 @@
     -->
 
     <style name="Theme.Base.AppCompat.DialogWhenLarge.Base"
-           parent="android:Theme.Holo.DialogWhenLarge">
+           parent="Theme.Platform.AppCompat.DialogWhenLarge">
         <!-- Copy system flag values for our use -->
         <item name="windowActionBar">?android:attr/windowActionBar</item>
         <item name="actionBarSize">?android:attr/actionBarSize</item>
@@ -158,7 +175,7 @@
     </style>
 
     <style name="Theme.Base.AppCompat.Light.DialogWhenLarge.Base"
-           parent="android:Theme.Holo.Light.DialogWhenLarge">
+           parent="Theme.Platform.AppCompat.Light.DialogWhenLarge">
         <!-- Copy system flag values for our use -->
         <item name="windowActionBar">?android:attr/windowActionBar</item>
         <item name="actionBarSize">?android:attr/actionBarSize</item>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
new file mode 100644
index 0000000..e00b809
--- /dev/null
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -0,0 +1,241 @@
+<?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.
+-->
+
+<resources>
+
+    <!-- Like in themes_base.xml, the namespace "*.AppCompat.Base" is used to
+     define base styles for the platform version. The "*.AppCompat"
+     variants are for direct use or use as parent styles by the app. -->
+    <eat-comment/>
+
+    <style name="Widget.AppCompat.Base.ActionBar"
+           parent="android:Widget.Material.ActionBar">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar"
+           parent="android:Widget.Material.Light.ActionBar">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionBar.Solid"
+           parent="android:Widget.Material.ActionBar.Solid">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid"
+           parent="android:Widget.Material.Light.ActionBar.Solid">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid.Inverse"
+           parent="android:Widget.Material.Light.ActionBar.Solid">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionBar.TabBar"
+           parent="android:Widget.Material.ActionBar.TabBar">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar"
+           parent="android:Widget.Material.Light.ActionBar.TabBar">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar.Inverse"
+           parent="android:Widget.Material.Light.ActionBar.TabBar">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionBar.TabView"
+           parent="android:Widget.Material.ActionBar.TabView">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView"
+           parent="android:Widget.Material.Light.ActionBar.TabView">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView.Inverse"
+           parent="android:Widget.Material.Light.ActionBar.TabView">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionBar.TabText"
+           parent="android:Widget.Material.ActionBar.TabText">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText"
+           parent="android:Widget.Material.Light.ActionBar.TabText">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText.Inverse"
+           parent="android:Widget.Material.Light.ActionBar.TabText">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionMode.Inverse"
+           parent="android:Widget.Material.Light.ActionMode">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Menu"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Menu">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Title">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Subtitle">
+    </style>
+
+
+    <!--
+    TODO Hidden
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Title.Inverse">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Subtitle.Inverse">
+    </style>
+    -->
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Title">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Subtitle">
+    </style>
+
+    <!--
+    TODO Hidden
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Title.Inverse">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse">
+    </style>
+    -->
+
+    <!-- Action Button Styles -->
+
+    <style name="Widget.AppCompat.Base.ActionButton"
+           parent="android:Widget.Material.ActionButton">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionButton"
+           parent="android:Widget.Material.Light.ActionButton">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionButton.CloseMode"
+           parent="android:Widget.Material.ActionButton.CloseMode">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionButton.CloseMode"
+           parent="android:Widget.Material.Light.ActionButton.CloseMode">
+    </style>
+
+    <style name="Widget.AppCompat.Base.ActionButton.Overflow"
+           parent="android:Widget.Material.ActionButton.Overflow">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ActionButton.Overflow"
+           parent="android:Widget.Material.Light.ActionButton.Overflow">
+    </style>
+
+    <!-- Spinner Widgets -->
+
+    <style name="Widget.AppCompat.Base.ListView.DropDown"
+           parent="android:Widget.Material.ListView.DropDown"/>
+
+    <style name="Widget.AppCompat.Light.Base.ListView.DropDown"
+           parent="android:Widget.Material.ListView.DropDown"/>
+
+    <style name="Widget.AppCompat.Base.DropDownItem.Spinner"
+           parent="android:Widget.Material.DropDownItem.Spinner"/>
+
+    <style name="Widget.AppCompat.Light.Base.DropDownItem.Spinner"
+           parent="android:Widget.Material.Light.DropDownItem.Spinner"/>
+
+    <style name="Widget.AppCompat.Base.Spinner"
+           parent="android:Widget.Material.Spinner" />
+
+    <style name="Widget.AppCompat.Light.Base.Spinner"
+           parent="android:Widget.Material.Light.Spinner"/>
+
+    <style name="Widget.AppCompat.Base.ListView.Menu"
+           parent="android:Widget.ListView.Menu" />
+
+    <!-- Popup Menu -->
+
+    <style name="Widget.AppCompat.Base.ListPopupWindow"
+           parent="android:Widget.Material.ListPopupWindow">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.ListPopupWindow"
+           parent="android:Widget.Material.Light.ListPopupWindow">
+    </style>
+
+    <style name="Widget.AppCompat.Base.PopupMenu" parent="android:Widget.Material.PopupMenu">
+    </style>
+
+    <style name="Widget.AppCompat.Light.Base.PopupMenu"
+        parent="android:Widget.Material.Light.PopupMenu">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Large"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Small"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Large"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Small"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
+    </style>
+
+    <!-- Search View result styles -->
+
+    <style name="TextAppearance.AppCompat.Base.SearchResult.Title"
+           parent="@android:TextAppearance.Material.SearchResult.Title">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Base.SearchResult.Subtitle"
+           parent="@android:TextAppearance.Material.SearchResult.Subtitle">
+    </style>
+
+    <!--
+        TextAppearance.Material.Light.SearchResult.* are private so we extend from the default
+        versions instead (which are exactly the same).
+    -->
+    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Title"
+           parent="TextAppearance.AppCompat.Base.SearchResult.Title">
+    </style>
+
+    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Subtitle"
+           parent="TextAppearance.AppCompat.Base.SearchResult.Subtitle">
+    </style>
+
+    <!-- TODO. Needs updating for QP -->
+    <style name="Widget.AppCompat.Base.ActivityChooserView" parent="">
+        <item name="android:gravity">center</item>
+        <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
+        <item name="android:divider">?attr/dividerVertical</item>
+        <item name="android:showDividers">middle</item>
+        <item name="android:dividerPadding">6dip</item>
+    </style>
+
+</resources>
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
new file mode 100644
index 0000000..6155225
--- /dev/null
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+
+<resources>
+
+    <!--
+        Theme in the "Theme.Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version. They should not set any styleable attributes. Instead
+        you should create a "Theme.Base" theme which inherits from a "Theme.Platform" theme.
+    -->
+    <eat-comment/>
+    <style name="Theme.Platform.AppCompat" parent="android:Theme.Material" />
+
+    <style name="Theme.Platform.AppCompat.Light" parent="android:Theme.Material.Light" />
+
+    <style name="Theme.Platform.AppCompat.Light.DarkActionBar"
+           parent="android:Theme.Material.Light.DarkActionBar" />
+
+    <style name="Theme.Platform.AppCompat.DialogWhenLarge"
+           parent="android:Theme.Material.DialogWhenLarge" />
+
+    <style name="Theme.Platform.AppCompat.Light.DialogWhenLarge"
+           parent="android:Theme.Material.Light.DialogWhenLarge" />
+
+    <style name="Theme.Platform.AppCompat.Dialog"
+           parent="android:Theme.Material.Dialog" />
+
+    <style name="Theme.Platform.AppCompat.Light.Dialog"
+           parent="android:Theme.Material.Light.Dialog" />
+
+</resources>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index c48b57c..f707c3a 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -16,6 +16,23 @@
 
 <resources>
 
+    <!--
+        Theme in the "Theme.Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version. They should not set any styleable attributes. Instead
+        you should create a "Theme.Base" theme which inherits from a "Theme.Platform" theme.
+    -->
+    <eat-comment/>
+    <style name="Theme.Platform.AppCompat" parent="android:Theme" />
+
+    <style name="Theme.Platform.AppCompat.Light" parent="android:Theme.Light" />
+
+    <style name="Theme.Platform.AppCompat.Dialog"
+           parent="android:Theme.Dialog" />
+
+    <style name="Theme.Platform.AppCompat.Light.Dialog"
+           parent="Theme.Platform.AppCompat.Dialog" />
+
+
     <!-- Themes in the "Theme.Base" family vary based on the current platform
          version to provide the correct basis on each device. You probably don't
          want to use them directly in your apps.
@@ -24,10 +41,10 @@
          directly by apps. -->
     <eat-comment/>
 
-    <style name="Theme.Base" parent="android:Theme">
+    <style name="Theme.Base" parent="Theme.Platform.AppCompat">
     </style>
 
-    <style name="Theme.Base.Light" parent="android:Theme.Light">
+    <style name="Theme.Base.Light" parent="Theme.Platform.AppCompat.Light">
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a dark-themed activity. -->
@@ -206,7 +223,7 @@
            parent="Theme.Base.AppCompat.Light">
     </style>
 
-    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="android:Theme.Dialog">
+    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="Theme.Platform.AppCompat.Dialog">
         <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
         <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
         <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 3af0dd4..d4dae83 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -40,7 +40,7 @@
 }
 
 android {
-    compileSdkVersion 19
+    compileSdkVersion 'current'
     buildToolsVersion '19.0.3'
 
     sourceSets {
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index f7d0607..40a58ad 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -10,6 +10,10 @@
     compileSdkVersion 7
     buildToolsVersion "19.0.1"
 
+    defaultConfig {
+        minSdkVersion 7
+    }
+
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
         main.java.srcDir 'src'
@@ -21,4 +25,71 @@
         // TODO: fix errors and reenable.
         abortOnError false
     }
-}
\ No newline at end of file
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support RecyclerView v7'
+                description "Android Support RecyclerView v7"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index dad6438..92d37a5 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -219,6 +219,21 @@
     public void endAnimation(ViewHolder item) {
         final View view = item.itemView;
         ViewCompat.animate(view).cancel();
+        if (mPendingMoves.contains(item)) {
+            ViewCompat.setTranslationY(view, 0);
+            ViewCompat.setTranslationX(view, 0);
+            dispatchMoveFinished(item);
+            mPendingMoves.remove(item);
+        }
+        if (mPendingRemovals.contains(item)) {
+            dispatchRemoveFinished(item);
+            mPendingRemovals.remove(item);
+        }
+        if (mPendingAdditions.contains(item)) {
+            ViewCompat.setAlpha(view, 1);
+            dispatchAddFinished(item);
+            mPendingAdditions.remove(item);
+        }
         if (mMoveAnimations.contains(item)) {
             ViewCompat.setTranslationY(view, 0);
             ViewCompat.setTranslationX(view, 0);
@@ -260,10 +275,34 @@
 
     @Override
     public void endAnimations() {
+        int count = mPendingMoves.size();
+        for (int i = count - 1; i >= 0; i--) {
+            MoveInfo item = mPendingMoves.get(i);
+            View view = item.holder.itemView;
+            ViewCompat.animate(view).cancel();
+            ViewCompat.setTranslationY(view, 0);
+            ViewCompat.setTranslationX(view, 0);
+            dispatchMoveFinished(item.holder);
+            mPendingMoves.remove(item);
+        }
+        count = mPendingRemovals.size();
+        for (int i = count - 1; i >= 0; i--) {
+            ViewHolder item = mPendingRemovals.get(i);
+            dispatchRemoveFinished(item);
+            mPendingRemovals.remove(item);
+        }
+        count = mPendingAdditions.size();
+        for (int i = count - 1; i >= 0; i--) {
+            ViewHolder item = mPendingAdditions.get(i);
+            View view = item.itemView;
+            ViewCompat.setAlpha(view, 1);
+            dispatchAddFinished(item);
+            mPendingAdditions.remove(item);
+        }
         if (!isRunning()) {
             return;
         }
-        int count = mMoveAnimations.size();
+        count = mMoveAnimations.size();
         for (int i = count - 1; i >= 0; i--) {
             ViewHolder item = mMoveAnimations.get(i);
             View view = item.itemView;
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index 2e3b739..91c75af 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -72,7 +72,7 @@
      * Based on {@link #mOrientation}, an implementation is lazily created in
      * {@link #ensureRenderState} method.
      */
-    private OrientationHelper mOrientationHelper;
+    OrientationHelper mOrientationHelper;
 
     /**
      * We need to track this so that we can ignore current position when it changes.
@@ -152,9 +152,7 @@
         SavedState state = new SavedState();
         if (getChildCount() > 0) {
             boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
-            state.mOrientation = mOrientation;
             state.mAnchorLayoutFromEnd = didLayoutFromEnd;
-
             if (didLayoutFromEnd) {
                 final View refChild = getChildClosestToEnd();
                 state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() -
@@ -172,6 +170,7 @@
         }
         state.mStackFromEnd = mStackFromEnd;
         state.mReverseLayout = mReverseLayout;
+        state.mOrientation = mOrientation;
         return state;
     }
 
@@ -208,6 +207,13 @@
      * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
      */
     public void setStackFromEnd(boolean stackFromEnd) {
+        if (mPendingSavedState != null && mPendingSavedState.mStackFromEnd != stackFromEnd) {
+            // override pending state
+            mPendingSavedState.mStackFromEnd = stackFromEnd;
+        }
+        if (mStackFromEnd == stackFromEnd) {
+            return;
+        }
         mStackFromEnd = stackFromEnd;
         requestLayout();
     }
@@ -237,6 +243,10 @@
         if (orientation != HORIZONTAL && orientation != VERTICAL) {
             throw new IllegalArgumentException("invalid orientation.");
         }
+        if (mPendingSavedState != null && mPendingSavedState.mOrientation != orientation) {
+            // override pending state
+            mPendingSavedState.mOrientation = orientation;
+        }
         if (orientation == mOrientation) {
             return;
         }
@@ -284,6 +294,10 @@
      * {@link #setStackFromEnd(boolean)}
      */
     public void setReverseLayout(boolean reverseLayout) {
+        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
+            // override pending state
+            mPendingSavedState.mReverseLayout = reverseLayout;
+        }
         if (reverseLayout == mReverseLayout) {
             return;
         }
@@ -665,7 +679,7 @@
         return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
     }
 
-    private void ensureRenderState() {
+    void ensureRenderState() {
         if (mRenderState == null) {
             mRenderState = new RenderState();
         }
@@ -984,6 +998,9 @@
         while (remainingSpace > 0 && renderState.hasMore(state)) {
             View view = renderState.next(recycler);
             if (view == null) {
+                if (DEBUG && renderState.mScrapList == null) {
+                    throw new RuntimeException("received null view when unexpected");
+                }
                 // if we are laying out views in scrap, this may return null which means there is
                 // no more items to layout.
                 break;
@@ -1122,6 +1139,99 @@
         return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
     }
 
+    /**
+     * Returns the adapter position of the first visible view.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views
+     * are ignored in this method.
+     *
+     * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
+     * there aren't any visible items.
+     * @see #findFirstCompletelyVisibleItemPosition()
+     * @see #findLastVisibleItemPosition()
+     */
+    public int findFirstVisibleItemPosition() {
+        return findOneVisibleChild(0, getChildCount(), false);
+    }
+
+    /**
+     * Returns the adapter position of the first fully visible view.
+     * <p>
+     * Note that bounds check is only performed in the current orientation. That means, if
+     * LinearLayoutManager is horizontal, it will only check the view's left and right edges.
+     *
+     * @return The adapter position of the first fully visible item or
+     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+     * @see #findFirstVisibleItemPosition()
+     * @see #findLastCompletelyVisibleItemPosition()
+     */
+    public int findFirstCompletelyVisibleItemPosition() {
+        return findOneVisibleChild(0, getChildCount(), true);
+    }
+
+    /**
+     * Returns the adapter position of the last visible view.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views
+     * are ignored in this method.
+     *
+     * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
+     * there aren't any visible items.
+     * @see #findLastCompletelyVisibleItemPosition()
+     * @see #findFirstVisibleItemPosition()
+     */
+    public int findLastVisibleItemPosition() {
+        return findOneVisibleChild(getChildCount() - 1, -1, false);
+    }
+
+    /**
+     * Returns the adapter position of the last fully visible view.
+     * <p>
+     * Note that bounds check is only performed in the current orientation. That means, if
+     * LinearLayoutManager is horizontal, it will only check the view's left and right edges.
+     *
+     * @return The adapter position of the last fully visible view or
+     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+     * @see #findLastVisibleItemPosition()
+     * @see #findFirstCompletelyVisibleItemPosition()
+     */
+    public int findLastCompletelyVisibleItemPosition() {
+        return findOneVisibleChild(getChildCount() - 1, -1, true);
+    }
+
+    int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
+        final int start = mOrientationHelper.getStartAfterPadding();
+        final int end = mOrientationHelper.getEndAfterPadding();
+        final int next = toIndex > fromIndex ? 1 : -1;
+        for (int i = fromIndex; i != toIndex; i+=next) {
+            final View child = getChildAt(i);
+            final int childStart = mOrientationHelper.getDecoratedStart(child);
+            final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+            if (childStart < end && childEnd > start) {
+                if (completelyVisible) {
+                    if (childStart >= start && childEnd <= end) {
+                        return getPosition(child);
+                    }
+                } else {
+                    return getPosition(child);
+                }
+            }
+        }
+        return RecyclerView.NO_POSITION;
+    }
+
     @Override
     public View onFocusSearchFailed(View focused, int focusDirection,
             RecyclerView.Recycler recycler,
@@ -1358,7 +1468,7 @@
         }
     }
 
-    private OrientationHelper createVerticalOrientationHelper() {
+    OrientationHelper createVerticalOrientationHelper() {
         return new OrientationHelper() {
             @Override
             public int getEndAfterPadding() {
@@ -1410,7 +1520,7 @@
         };
     }
 
-    private OrientationHelper createHorizontalOrientationHelper() {
+    OrientationHelper createHorizontalOrientationHelper() {
         return new OrientationHelper() {
             @Override
             public int getEndAfterPadding() {
@@ -1466,7 +1576,7 @@
     /**
      * Helper interface to offload orientation based decisions
      */
-    private static interface OrientationHelper {
+    static interface OrientationHelper {
 
         /**
          * @param view The view element to check
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
index 295aac7..ed4c950 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
@@ -108,7 +108,9 @@
         final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
         final int distance = (int) Math.sqrt(dx * dx + dy * dy);
         final int time = calculateTimeForDeceleration(distance);
-        action.update(-dx, -dy, time, mDecelerateInterpolator);
+        if (time > 0) {
+            action.update(-dx, -dy, time, mDecelerateInterpolator);
+        }
     }
 
     /**
@@ -168,7 +170,7 @@
         // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
         // which gives 0.100028 when x = .3356
         // this is why we divide linear scrolling time with .3356
-        return (int) (calculateTimeForScrolling(dx) / .3356);
+        return  (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
     }
 
     /**
@@ -179,7 +181,10 @@
      * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
      */
     protected int calculateTimeForScrolling(int dx) {
-        return Math.round(Math.abs(dx) * MILLISECONDS_PER_PX);
+        // In a case where dx is very small, rounding may return 0 although dx > 0.
+        // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
+        // time.
+        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
     }
 
     /**
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 73a9e62..b9cf895 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -104,6 +104,9 @@
      */
     private final Runnable mUpdateChildViewsRunnable = new Runnable() {
         public void run() {
+            if (mPendingUpdates.isEmpty()) {
+                return;
+            }
             eatRequestLayout();
             updateChildViews();
             resumeRequestLayout(true);
@@ -252,6 +255,18 @@
         if (mAdapter != null) {
             mAdapter.unregisterAdapterDataObserver(mObserver);
         }
+        // end all running animations
+        if (mItemAnimator != null) {
+            mItemAnimator.endAnimations();
+        }
+        // Since animations are ended, mLayout.children should be equal to recyclerView.children.
+        // This may not be true if item animator's end does not work as expected. (e.g. not release
+        // children instantly). It is safer to use mLayout's child count.
+        if (mLayout != null) {
+            mLayout.removeAndRecycleAllViews(mRecycler);
+            mLayout.removeAndRecycleScrapInt(mRecycler, true);
+        }
+
         final Adapter oldAdapter = mAdapter;
         mAdapter = adapter;
         if (adapter != null) {
@@ -406,7 +421,8 @@
             for (int i = mAnimatingViewIndex; i < getChildCount(); ++i) {
                 final View view = getChildAt(i);
                 ViewHolder holder = getChildViewHolder(view);
-                if (holder.getPosition() == position && holder.getItemViewType() == type) {
+                if (holder.getPosition() == position &&
+                        ( type == INVALID_TYPE || holder.getItemViewType() == type)) {
                     return view;
                 }
             }
@@ -571,21 +587,22 @@
     }
 
     /**
-     * <p>Starts a smooth scroll to an adapter position.</p>
-     *
-     * <p>To support smooth scrolling, you must override
-     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.Adapter, int)} and
-     * create a {@link SmoothScroller}.</p>
-     * <p>{@link LayoutManager} is responsible for creating the actual scroll action. If you want to
+     * Starts a smooth scroll to an adapter position.
+     * <p>
+     * To support smooth scrolling, you must override
+     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
+     * {@link SmoothScroller}.
+     * <p>
+     * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
      * provide a custom smooth scroll logic, override
-     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.Adapter, int)} in your
-     * LayoutManager.</p>
+     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
+     * LayoutManager.
      *
      * @param position The adapter position to scroll to
-     * @see LayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.Adapter, int)
+     * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
      */
     public void smoothScrollToPosition(int position) {
-        mLayout.smoothScrollToPosition(this, mAdapter, position);
+        mLayout.smoothScrollToPosition(this, mState, position);
     }
 
     @Override
@@ -608,13 +625,31 @@
     }
 
     /**
-     * Does not perform bounds checking. Used by internal methods that have already validated input.
+     * Helper method reflect data changes to the state.
+     * <p>
+     * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
+     * but data actually changed.
+     * <p>
+     * This method consumes all deferred changes to avoid that case.
+     * <p>
+     * This also ends all pending animations. It will be changed once we can support
+     * animations during scroll.
      */
-    void scrollByInternal(int x, int y) {
+    private void consumePendingUpdateOperations() {
         if (mItemAnimator != null) {
             mItemAnimator.endAnimations();
         }
+        if (mPendingUpdates.size() > 0) {
+            mUpdateChildViewsRunnable.run();
+        }
+    }
+
+    /**
+     * Does not perform bounds checking. Used by internal methods that have already validated input.
+     */
+    void scrollByInternal(int x, int y) {
         int overscrollX = 0, overscrollY = 0;
+        consumePendingUpdateOperations();
         if (mAdapter != null) {
             eatRequestLayout();
             if (x != 0) {
@@ -653,8 +688,8 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset(
-     * RecyclerView.Adapter)} in your LayoutManager. </p>
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
+     * LayoutManager. </p>
      *
      * @return The horizontal offset of the scrollbar's thumb
      * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
@@ -662,7 +697,7 @@
      */
     @Override
     protected int computeHorizontalScrollOffset() {
-        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mAdapter)
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState)
                 : 0;
     }
 
@@ -677,17 +712,15 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollExtent(
-     * RecyclerView.Adapter)} in your LayoutManager.</p>
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
+     * LayoutManager.</p>
      *
      * @return The horizontal extent of the scrollbar's thumb
-     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollExtent(
-     * RecyclerView.Adapter)
+     * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
      */
     @Override
     protected int computeHorizontalScrollExtent() {
-        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mAdapter)
-                : 0;
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
     }
 
     /**
@@ -699,16 +732,15 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollRange(
-     * RecyclerView.Adapter)} in your LayoutManager.</p>
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
+     * LayoutManager.</p>
      *
      * @return The total horizontal range represented by the vertical scrollbar
-     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollRange(
-     * RecyclerView.Adapter)
+     * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
      */
     @Override
     protected int computeHorizontalScrollRange() {
-        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mAdapter) : 0;
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
     }
 
     /**
@@ -721,8 +753,8 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset(
-     * RecyclerView.Adapter)} in your LayoutManager.</p>
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
+     * LayoutManager.</p>
      *
      * @return The vertical offset of the scrollbar's thumb
      * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
@@ -730,7 +762,7 @@
      */
     @Override
     protected int computeVerticalScrollOffset() {
-        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mAdapter) : 0;
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
     }
 
     /**
@@ -743,16 +775,15 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollExtent(
-     * RecyclerView.Adapter)} in your LayoutManager.</p>
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
+     * LayoutManager.</p>
      *
      * @return The vertical extent of the scrollbar's thumb
-     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollExtent(
-     * RecyclerView.Adapter)
+     * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
      */
     @Override
     protected int computeVerticalScrollExtent() {
-        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mAdapter) : 0;
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
     }
 
     /**
@@ -764,16 +795,15 @@
      * <p>Default implementation returns 0.</p>
      *
      * <p>If you want to support scroll bars, override
-     * {@link android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollRange(
-     * RecyclerView.Adapter)} in your LayoutManager.</p>
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
+     * LayoutManager.</p>
      *
      * @return The total vertical range represented by the vertical scrollbar
-     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollRange(
-     * RecyclerView.Adapter)
+     * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
      */
     @Override
     protected int computeVerticalScrollRange() {
-        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mAdapter) : 0;
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
     }
 
 
@@ -943,7 +973,7 @@
         result = ff.findNextFocus(this, focused, direction);
         if (result == null && mAdapter != null) {
             eatRequestLayout();
-            result = mLayout.onFocusSearchFailed(focused, direction, mAdapter, mRecycler);
+            result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
             resumeRequestLayout(false);
         }
         return result != null ? result : super.focusSearch(focused, direction);
@@ -962,7 +992,7 @@
 
     @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
-        return mLayout.requestChildRectangleOnScreen(child, rect, immediate);
+        return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
     }
 
     @Override
@@ -1273,7 +1303,10 @@
             resumeRequestLayout(false);
         }
 
-        mLayout.onMeasure(widthSpec, heightSpec);
+        if (mAdapter != null) {
+            mState.mItemCount = mAdapter.getItemCount();
+        }
+        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
 
         final int widthSize = getMeasuredWidth();
         final int heightSize = getMeasuredHeight();
@@ -1376,7 +1409,7 @@
             mState.mPostLayoutHolderMap.clear();
             int count = getChildCount();
             for (int i = 0; i < count; ++i) {
-                final ViewHolder holder = getViewHolderForChildAt(i);
+                final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
                 final View view = holder.itemView;
                 mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
                         view.getLeft(), view.getTop(), view.getRight(), view.getBottom(),
@@ -1390,7 +1423,7 @@
             mInPreLayout = true;
             final boolean didStructureChange = mState.mStructureChanged;
             mState.mStructureChanged = false;
-            // temporarly disable flag because we are asking for previous layout
+            // temporarily disable flag because we are asking for previous layout
             mLayout.onLayoutChildren(mRecycler, mState);
             mState.mStructureChanged = didStructureChange;
             mInPreLayout = false;
@@ -1427,7 +1460,7 @@
             // Step 3: Find out where things are now, post-layout
             int count = getChildCount();
             for (int i = 0; i < count; ++i) {
-                ViewHolder holder = getViewHolderForChildAt(i);
+                ViewHolder holder = getChildViewHolderInt(getChildAt(i));
                 final View view = holder.itemView;
                 mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
                         view.getLeft(), view.getTop(), view.getRight(), view.getBottom(),
@@ -1487,7 +1520,7 @@
             }
         }
         resumeRequestLayout(false);
-        mLayout.removeAndRecycleScrapInt(mRecycler);
+        mLayout.removeAndRecycleScrapInt(mRecycler, !animateChanges);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
     }
@@ -1947,25 +1980,6 @@
     }
 
     /**
-     * Return the ViewHolder for the child view positioned underneath the coordinates (x, y).
-     *
-     * @param x Horizontal position in pixels to search
-     * @param y Vertical position in pixels to search
-     * @return The ViewHolder for the child under (x, y) or null if no child is found
-     *
-     * @deprecated This method will be removed. Use {@link #findChildViewUnder(float, float)}
-     *             along with {@link #getChildViewHolder(View)}
-     */
-    @Deprecated
-    public ViewHolder findViewHolderForChildUnder(int x, int y) {
-        final View child = findChildViewUnder(x, y);
-        if (child != null) {
-            return getChildViewHolderInt(child);
-        }
-        return null;
-    }
-
-    /**
      * Find the topmost view under the given point.
      *
      * @param x Horizontal position in pixels to search
@@ -1989,20 +2003,6 @@
     }
 
     /**
-     * Return the ViewHolder for the child view of the RecyclerView that is at the
-     * given index.
-     *
-     * @param childIndex The index of the child in the RecyclerView's child list.
-     * @return The ViewHolder for the given <code>childIndex</code>
-     *
-     * @deprecated Use {@link #getChildViewHolder(View)} and {@link #getChildAt(int)}
-     */
-    @Deprecated
-    public ViewHolder getViewHolderForChildAt(int childIndex) {
-        return getChildViewHolderInt(getChildAt(childIndex));
-    }
-
-    /**
      * Offset the bounds of all child views by <code>dy</code> pixels.
      * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
      *
@@ -2094,6 +2094,7 @@
         @Override
         public void run() {
             disableRunOnAnimationRequests();
+            consumePendingUpdateOperations();
             // keep a local reference so that if it is changed during onAnimation method, it wont cause
             // unexpected behaviors
             final ScrollerCompat scroller = mScroller;
@@ -2300,11 +2301,6 @@
             mScrap.clear();
         }
 
-        /** @deprecated No longer needed */
-        @Deprecated
-        public void reset(int typeCount) {
-        }
-
         public void setMaxRecycledViews(int viewType, int max) {
             mMaxScrap.put(viewType, max);
             final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
@@ -2401,7 +2397,7 @@
          */
         public void clear() {
             mAttachedScrap.clear();
-            mCachedViews.clear();
+            recycleCachedViews();
         }
 
         /**
@@ -2426,8 +2422,40 @@
         }
 
         /**
+         * Helper method for getViewForPosition.
+         * <p>
+         * Checks whether a given view holder can be used for the provided position.
+         *
+         * @param holder         ViewHolder
+         * @param offsetPosition The position which is updated by UPDATE_OP changes on the adapter
+         * @return true if ViewHolder matches the provided position, false otherwise
+         */
+        boolean validateViewHolderForOffsetPosition(ViewHolder holder, int offsetPosition) {
+            // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
+            // if it is not removed, verify the type and id.
+            if (holder.isRemoved()) {
+                return true;
+            }
+            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+                if (DEBUG) {
+                    Log.d(TAG, "validateViewHolderForOffsetPosition: invalid position, returning "
+                            + "false");
+                }
+                return false;
+            }
+            final int type = mAdapter.getItemViewType(offsetPosition);
+            if (type != holder.getItemViewType()) {
+                return false;
+            }
+            if (mAdapter.hasStableIds()) {
+                return holder.getItemId() == mAdapter.getItemId(offsetPosition);
+            }
+            return true;
+        }
+
+        /**
          * Obtain a view initialized for the given position.
-         * 
+         *
          * <p>This method should be used by {@link LayoutManager} implementations to obtain
          * views to represent data from an {@link Adapter}.</p>
          *
@@ -2440,42 +2468,53 @@
          * @return A view representing the data at <code>position</code> from <code>adapter</code>
          */
         public View getViewForPosition(int position) {
-            return getViewForPosition(mAdapter, position);
-        }
-
-        /**
-         * @deprecated  use getViewForPosition(int position)
-         */
-        @Deprecated
-        public View getViewForPosition(Adapter adapter, int position) {
             ViewHolder holder;
+            holder = getScrapViewForPosition(position, INVALID_TYPE);
+            final int offsetPosition = findPositionOffset(position);
+            if (holder != null) {
+                if (!validateViewHolderForOffsetPosition(holder, offsetPosition)) {
+                    // recycle this scrap
+                    removeDetachedView(holder.itemView, false);
+                    quickRecycleScrapView(holder.itemView);
 
-            final int type = adapter.getItemViewType(position);
-            if (adapter.hasStableIds()) {
-                final long id = adapter.getItemId(position);
-                holder = getScrapViewForId(id, type);
+                    // if validate fails, we can query scrap again w/ type. that may return a
+                    // different view holder from cache.
+                    final int type = mAdapter.getItemViewType(offsetPosition);
+                    if (mAdapter.hasStableIds()) {
+                        final long id = mAdapter.getItemId(offsetPosition);
+                        holder = getScrapViewForId(id, type);
+                    } else {
+                        holder = getScrapViewForPosition(offsetPosition, type);
+                    }
+                }
             } else {
-                holder = getScrapViewForPosition(position, type);
+                // try recycler.
+                holder = getRecycledViewPool()
+                        .getRecycledView(mAdapter.getItemViewType(offsetPosition));
             }
 
             if (holder == null) {
-                holder = adapter.createViewHolder(RecyclerView.this, type);
-                if (DEBUG) Log.d(TAG, "getViewForPosition created new ViewHolder");
+                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+                    throw new IndexOutOfBoundsException("Invalid item position " + position
+                            + "(" + offsetPosition + ")");
+                } else {
+                    holder = mAdapter.createViewHolder(RecyclerView.this,
+                            mAdapter.getItemViewType(offsetPosition));
+                    if (DEBUG) {
+                        Log.d(TAG, "getViewForPosition created new ViewHolder");
+                    }
+                }
             }
 
-            if (!holder.isBound() || holder.needsUpdate()) {
+            if (!holder.isRemoved() && (!holder.isBound() || holder.needsUpdate())) {
                 if (DEBUG) {
                     Log.d(TAG, "getViewForPosition unbound holder or needs update; updating...");
                 }
-                int offsetPosition = findPositionOffset(position);
-                if (offsetPosition < 0 || offsetPosition >= adapter.getItemCount()) {
-                    if (DEBUG) Log.d(TAG, "getViewForPosition: invalid position, returning null");
-                    return null;
-                }
+
                 // TODO: think through when getOffsetPosition() is called. I use it here because
                 // existing views have already been offset appropriately through the mOldOffset
                 // mechanism, but new views do not have this mechanism.
-                adapter.bindViewHolder(holder, offsetPosition);
+                mAdapter.bindViewHolder(holder, offsetPosition);
             }
 
             ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
@@ -2492,16 +2531,6 @@
         }
 
         /**
-         * @deprecated Renamed to {@link #recycleView(android.view.View)} to cause
-         * less confusion between temporarily detached scrap views and fully detached
-         * recycled views. This method will be removed.
-         */
-        @Deprecated
-        public void addDetachedScrapView(View scrap) {
-            recycleView(scrap);
-        }
-
-        /**
          * Recycle a detached view. The specified view will be added to a pool of views
          * for later rebinding and reuse.
          *
@@ -2513,14 +2542,26 @@
             recycleViewHolder(getChildViewHolderInt(view));
         }
 
+        void recycleCachedViews() {
+            final int count = mCachedViews.size();
+            for (int i = count - 1; i >= 0; i--) {
+                final ViewHolder cachedView = mCachedViews.get(i);
+                if (cachedView.isRecyclable()) {
+                    getRecycledViewPool().putRecycledView(cachedView);
+                    dispatchViewRecycled(cachedView);
+                }
+                mCachedViews.remove(i);
+            }
+        }
+
         void recycleViewHolder(ViewHolder holder) {
             if (holder.isScrap() || holder.itemView.getParent() != null) {
                 throw new IllegalArgumentException(
                         "Scrapped or attached views may not be recycled.");
             }
 
-            if (mCachedViews.size() < mViewCacheMax && !holder.isInvalid() &&
-                    (mInPreLayout || !holder.isRemoved())) {
+            boolean cached = false;
+            if (!holder.isInvalid() && (mInPreLayout || !holder.isRemoved())) {
                 // Retire oldest cached views first
                 if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
                     for (int i = 0; i < mCachedViews.size(); i++) {
@@ -2533,8 +2574,12 @@
                         }
                     }
                 }
-                mCachedViews.add(holder);
-            } else if (holder.isRecyclable()) {
+                if (mCachedViews.size() < mViewCacheMax) {
+                    mCachedViews.add(holder);
+                    cached = true;
+                }
+            }
+            if (!cached && holder.isRecyclable()) {
                 getRecycledViewPool().putRecycledView(holder);
                 dispatchViewRecycled(holder);
             }
@@ -2556,31 +2601,6 @@
         }
 
         /**
-         * @deprecated This method will be removed. Adding and removing views is the responsibility
-         * of the LayoutManager; the Recycler will only be responsible for marking and tracking
-         * views for reuse. This method no longer matches the definition of 'scrap'.
-         */
-        @Deprecated
-        public void detachAndScrapView(View scrap) {
-            if (scrap.getParent() != RecyclerView.this) {
-                throw new IllegalArgumentException("View " + scrap + " is not attached to " +
-                        RecyclerView.this);
-            }
-            mLayout.removeView(scrap);
-            recycleView(scrap);
-        }
-
-        /**
-         * @deprecated This method will be removed. Moved to
-         * {@link LayoutManager#detachAndScrapAttachedViews(android.support.v7.widget.RecyclerView.Recycler)}
-         * to keep LayoutManager as the owner of attach/detach operations.
-         */
-        @Deprecated
-        public void scrapAllViewsAttached() {
-            mLayout.detachAndScrapAttachedViews(this);
-        }
-
-        /**
          * Mark an attached view as scrap.
          *
          * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
@@ -2606,17 +2626,6 @@
             holder.mScrapContainer = null;
         }
 
-        /**
-         * @deprecated This method will be removed. Adding and removing views should be done
-         * through the LayoutManager. Use
-         * {@link LayoutManager#removeAndRecycleScrap(android.support.v7.widget.RecyclerView.Recycler)}
-         * instead.
-         */
-        @Deprecated
-        public void detachDirtyScrapViews() {
-            mLayout.removeAndRecycleScrap(this);
-        }
-
         int getScrapCount() {
             return mAttachedScrap.size();
         }
@@ -2629,6 +2638,14 @@
             mAttachedScrap.clear();
         }
 
+        /**
+         * Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if
+         * ViewHolder's type matches the provided type.
+         *
+         * @param position Item position
+         * @param type View type
+         * @return a ViewHolder that can be re-used for this position.
+         */
         ViewHolder getScrapViewForPosition(int position, int type) {
             final int scrapCount = mAttachedScrap.size();
 
@@ -2637,7 +2654,7 @@
                 final ViewHolder holder = mAttachedScrap.get(i);
                 if (holder.getPosition() == position && !holder.isInvalid() &&
                         (mInPreLayout || !holder.isRemoved())) {
-                    if (holder.getItemViewType() != type) {
+                    if (type != INVALID_TYPE && holder.getItemViewType() != type) {
                         Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
                                 " wrong view type! (found " + holder.getItemViewType() +
                                 " but expected " + type + ")");
@@ -2667,7 +2684,8 @@
                 final ViewHolder holder = mCachedViews.get(i);
                 if (holder.getPosition() == position) {
                     mCachedViews.remove(i);
-                    if (holder.isInvalid() && holder.getItemViewType() != type) {
+                    if (holder.isInvalid() &&
+                            (type != INVALID_TYPE && holder.getItemViewType() != type)) {
                         // Can't use it. We don't know where it's been.
                         if (DEBUG) {
                             Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
@@ -2699,7 +2717,7 @@
                 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
                     ") fetching from shared pool");
             }
-            return getRecycledViewPool().getRecycledView(type);
+            return type == INVALID_TYPE ? null : getRecycledViewPool().getRecycledView(type);
         }
 
         ViewHolder getScrapViewForId(long id, int type) {
@@ -2880,10 +2898,6 @@
         private final AdapterDataObservable mObservable = new AdapterDataObservable();
         private boolean mHasStableIds = false;
 
-        /** @deprecated */
-        @Deprecated
-        private int mViewTypeCount = 1;
-
         public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
         public abstract void onBindViewHolder(VH holder, int position);
 
@@ -2919,39 +2933,6 @@
             return 0;
         }
 
-        /**
-         * Set the number of item view types required by this adapter to display its data set.
-         * This may not be changed while the adapter has observers - e.g. while the adapter
-         * is set on a {#link RecyclerView}.
-         *
-         * @param count Number of item view types required
-         * @see #getItemViewTypeCount()
-         * @see #getItemViewType(int)
-         *
-         * @deprecated This method is no longer necessary. View types are now unbounded.
-         */
-        @Deprecated
-        public void setItemViewTypeCount(int count) {
-            Log.w(TAG, "setItemViewTypeCount is deprecated and no longer needed.");
-            mViewTypeCount = count;
-        }
-
-        /**
-         * Retrieve the number of item view types required by this adapter to display its data set.
-         *
-         * @return Number of item view types supported
-         * @see #setItemViewTypeCount(int)
-         * @see #getItemViewType(int)
-         *
-         * @deprecated This method is no longer necessary. View types are now unbounded.
-         */
-        @Deprecated
-        public final int getItemViewTypeCount() {
-            Log.w(TAG, "getItemViewTypeCount is no longer needed. " +
-                    "View type count is now unbounded.");
-            return mViewTypeCount;
-        }
-
         public void setHasStableIds(boolean hasStableIds) {
             if (hasObservers()) {
                 throw new IllegalStateException("Cannot change whether this adapter has " +
@@ -3218,16 +3199,6 @@
         SmoothScroller mSmoothScroller;
 
         /**
-         * @deprecated LayoutManagers should not access the RecyclerView they are bound to directly.
-         *             See the other methods on LayoutManager for accessing child views and
-         *             container properties instead. <em>This method will be removed.</em>
-         */
-        @Deprecated
-        public final RecyclerView getRecyclerView() {
-            return mRecyclerView;
-        }
-
-        /**
          * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
          */
         public void requestLayout() {
@@ -3279,35 +3250,6 @@
         }
 
         /**
-         * @deprecated Use
-         * {@link #onLayoutChildren(RecyclerView.Adapter, RecyclerView.Recycler, boolean,
-         * RecyclerView.State)}
-         */
-        @Deprecated
-        public void layoutChildren(Adapter adapter, Recycler recycler) {
-        }
-
-
-        /**
-         * @deprecated Use
-         * {@link #onLayoutChildren(Adapter, Recycler, boolean, RecyclerView.State)}
-         */
-        @Deprecated
-        public void layoutChildren(Adapter adapter, Recycler recycler, boolean structureChanged) {
-            layoutChildren(adapter, recycler);
-        }
-
-        /**
-         * @deprecated
-         * use onLayoutChildren(Recycler recycler, State state)
-         */
-        @Deprecated
-        public void onLayoutChildren(Adapter adapter, Recycler recycler, boolean structureChanged,
-                State state) {
-            layoutChildren(adapter, recycler, structureChanged);
-        }
-
-        /**
          * Lay out all relevant child views from the given adapter.
          *
          * <p>Special care should be taken when automatic animations of added/removed items
@@ -3344,7 +3286,6 @@
          */
         public void onLayoutChildren(Recycler recycler, State state) {
             Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
-            onLayoutChildren(mRecyclerView.mAdapter, recycler, state.mStructureChanged, state);
         }
 
         /**
@@ -3433,37 +3374,6 @@
          * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
          */
         public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
-            Log.e(TAG, "you must override "
-                    + "scrollHorizontallyBy(dx,recycler,state)");
-            return scrollHorizontallyBy(dx, mRecyclerView.mAdapter, recycler, state);
-        }
-
-        /**
-         * @deprecated
-         * use scrollHorizontallyBy(int dx, Recycler recycler, State state).
-         */
-        @Deprecated
-        public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler,
-                State state) {
-            return scrollHorizontallyBy(dx, adapter, recycler);
-        }
-
-
-        /**
-         * @deprecated Use
-         * {@link #scrollHorizontallyBy(int, Adapter, Recycler, RecyclerView.State)}
-         */
-        @Deprecated
-        public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler) {
-            return scrollHorizontallyBy(dx, recycler);
-        }
-
-        /**
-         * @deprecated Use
-         * {@link #scrollHorizontallyBy(int, Adapter, Recycler, RecyclerView.State)}
-         */
-        @Deprecated
-        public int scrollHorizontallyBy(int dx, Recycler recycler) {
             return 0;
         }
 
@@ -3481,37 +3391,6 @@
          * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
          */
         public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
-            Log.e(TAG, "you should override "
-                    + "scrollVerticallyBy(dx,adapter,recycler,state)");
-            return scrollVerticallyBy(dy, mRecyclerView.mAdapter, recycler, state);
-        }
-
-        /**
-         * @deprecated
-         * Override scrollVerticallyBy(int dy, Recycler recycler, State state) instead.
-         */
-        @Deprecated
-        public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler,
-                State state) {
-            return scrollVerticallyBy(dy, adapter, recycler);
-        }
-
-
-        /**
-         * @deprecated
-         * Override {@link #scrollVerticallyBy(int, Adapter, Recycler, RecyclerView.State)} instead.
-         */
-        @Deprecated
-        public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler) {
-            return scrollVerticallyBy(dy, recycler);
-        }
-
-        /**
-         * @deprecated API changed to include the Adapter to use. Override
-         * {@link #scrollVerticallyBy(int, Adapter, Recycler, RecyclerView.State)} instead.
-         */
-        @Deprecated
-        public int scrollVerticallyBy(int dy, Recycler recycler) {
             return 0;
         }
 
@@ -3548,15 +3427,6 @@
         }
 
         /**
-         * @deprecated
-         * use smoothScrollToPosition(RecyclerView recyclerView, State state, int position)
-         */
-        @Deprecated
-        public void smoothScrollToPosition(RecyclerView recyclerView, Adapter adapter,
-                int position) {
-        }
-
-        /**
          * <p>Smooth scroll to the specified adapter position.</p>
          * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
          * instance and call {@link #startSmoothScroll(SmoothScroller)}.
@@ -3607,7 +3477,7 @@
         /**
          * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
          * use this method to add views obtained from a {@link Recycler} using
-         * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}.
+         * {@link Recycler#getViewForPosition(int)}.
          *
          * @param child View to add
          * @param index Index to add child at
@@ -3638,7 +3508,7 @@
         /**
          * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
          * use this method to add views obtained from a {@link Recycler} using
-         * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}.
+         * {@link Recycler#getViewForPosition(int)}.
          *
          * @param child View to add
          */
@@ -3702,13 +3572,15 @@
             final Adapter adapter = mRecyclerView.getAdapter();
             // Only remove non-animating views
             final int childCount = mRecyclerView.getChildCount() - mRecyclerView.mNumAnimatingViews;
-            if (adapter != null) {
-                for (int i = 0; i < childCount; i++) {
-                    final View child = mRecyclerView.getChildAt(i);
+
+            for (int i = 0; i < childCount; i++) {
+                final View child = mRecyclerView.getChildAt(i);
+                if (adapter != null) {
                     adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
-                    mRecyclerView.onChildDetachedFromWindow(child);
                 }
+                mRecyclerView.onChildDetachedFromWindow(child);
             }
+
             for (int i = childCount - 1; i >= 0; i--) {
                 mRecyclerView.removeViewAt(i);
                 if (mRecyclerView.mAnimatingViewIndex >= 0) {
@@ -4062,27 +3934,29 @@
         }
 
         /**
-         * Remove and recycle all scrap views currently tracked by Recycler. Recycled views
-         * will be made available for later reuse.
+         * Recycles the scrapped views.
+         * <p>
+         * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is
+         * the expected behavior if scrapped views are used for animations. Otherwise, we need to
+         * call remove and invalidate RecyclerView to ensure UI update.
          *
-         * @param recycler Recycler tracking scrap views to remove
-         * @deprecated Scrap will get recycled automatically by the RecyclerView after every call
-         * to {@link #onLayoutChildren(Adapter, Recycler, boolean, State)}. This method now
-         * does nothing and should no longer be called by LayoutManager implementations.
+         * @param recycler Recycler
+         * @param remove   Whether scrapped views should be removed from ViewGroup or not. This
+         *                 method will invalidate RecyclerView if it removes any scrapped child.
          */
-        @Deprecated
-        public void removeAndRecycleScrap(Recycler recycler) {
-            // noop
-        }
-
-        void removeAndRecycleScrapInt(Recycler recycler) {
+        void removeAndRecycleScrapInt(Recycler recycler, boolean remove) {
             final int scrapCount = recycler.getScrapCount();
             for (int i = 0; i < scrapCount; i++) {
                 final View scrap = recycler.getScrapViewAt(i);
-                ViewHolder holder = mRecyclerView.getChildViewHolder(scrap);
+                if (remove) {
+                    mRecyclerView.removeDetachedView(scrap, false);
+                }
                 recycler.quickRecycleScrapView(scrap);
             }
             recycler.clearScrap();
+            if (remove && scrapCount > 0) {
+                mRecyclerView.invalidate();
+            }
         }
 
 
@@ -4313,37 +4187,6 @@
          */
         public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
                 State state) {
-            Log.e(TAG, "you must override onFocusSearchFailed(View focused, int direction, "
-                    + "Recycler recycler, State state)");
-            return onFocusSearchFailed(focused, direction, mRecyclerView.mAdapter, recycler, state);
-        }
-
-        /**
-         * @deprecated use onFocusSearchFailed(View focused, int direction, Recycler recycler,
-         * State state)
-         */
-        @Deprecated
-        public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
-                Recycler recycler, State state) {
-            return this.onFocusSearchFailed(focused, direction, adapter, recycler);
-        }
-
-        /**
-         * @deprecated Use {@link #onFocusSearchFailed(View, int, RecyclerView.Adapter,
-         * RecyclerView.Recycler, RecyclerView.State)}
-         */
-        @Deprecated
-        public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
-                Recycler recycler) {
-            return onFocusSearchFailed(focused, direction, recycler);
-        }
-
-        /**
-         * @deprecated API changed to supply the Adapter. Override
-         * {@link #onFocusSearchFailed(android.view.View, int, Adapter, Recycler)} instead.
-         */
-        @Deprecated
-        public View onFocusSearchFailed(View focused, int direction, Recycler recycler) {
             return null;
         }
 
@@ -4351,7 +4194,7 @@
          * This method gives a LayoutManager an opportunity to intercept the initial focus search
          * before the default behavior of {@link FocusFinder} is used. If this method returns
          * null FocusFinder will attempt to find a focusable child view. If it fails
-         * then {@link #onFocusSearchFailed(View, int, RecyclerView.Adapter, RecyclerView.Recycler)}
+         * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
          * will be called to give the LayoutManager an opportunity to add new views for items
          * that did not have attached views representing them. The LayoutManager should not add
          * or remove views from this method.
@@ -4368,15 +4211,6 @@
         }
 
         /**
-         * @deprecated This method will be removed. Override {@link #requestChildRectangleOnScreen(
-         * RecyclerView, android.view.View, android.graphics.Rect, boolean)} instead.
-         */
-        @Deprecated
-        public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
-            return requestChildRectangleOnScreen(mRecyclerView, child, rect, immediate);
-        }
-
-        /**
          * Called when a child of the RecyclerView wants a particular rectangle to be positioned
          * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
          * android.graphics.Rect, boolean)} for more details.
@@ -4446,30 +4280,10 @@
          * @return true if the default scroll behavior should be suppressed
          */
         public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
-            return onRequestChildFocus(child, focused);
-        }
-
-        /**
-         * @deprecated This method will be removed. Override
-         * {@link #onRequestChildFocus(RecyclerView, android.view.View, android.view.View)}
-         * instead.
-         */
-        @Deprecated
-        public boolean onRequestChildFocus(View child, View focused) {
             return false;
         }
 
         /**
-         * @deprecated This method will be removed. Override
-         * {@link #onAdapterChanged(RecyclerView.Adapter, RecyclerView.Adapter)}
-         * instead.
-         */
-        @Deprecated
-        public void onAdapterChanged() {
-            removeAllViews();
-        }
-
-        /**
          * 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.
@@ -4482,7 +4296,6 @@
          *                   {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
          */
         public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
-            onAdapterChanged();
         }
 
         /**
@@ -4538,15 +4351,6 @@
 
 
         /**
-         * @deprecated
-         * Override computeHorizontalScrollExtent(State state) instead.
-         */
-        @Deprecated
-        public int computeHorizontalScrollExtent(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
@@ -4562,15 +4366,6 @@
         }
 
         /**
-         * @deprecated
-         * Override computeHorizontalScrollOffset(State state) instead.
-         */
-        @Deprecated
-        public int computeHorizontalScrollOffset(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
@@ -4586,15 +4381,6 @@
         }
 
         /**
-         * @deprecated
-         * Override computeHorizontalScrollRange(State state) instead.
-         */
-        @Deprecated
-        public int computeHorizontalScrollRange(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
@@ -4610,15 +4396,6 @@
         }
 
         /**
-         * @deprecated
-         * Override computeVerticalScrollExtent(State state) instead.
-         */
-        @Deprecated
-        public int computeVerticalScrollExtent(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
@@ -4634,15 +4411,6 @@
         }
 
         /**
-         * @deprecated
-         * Override computeVerticalScrollOffset(State state) instead.
-         */
-        @Deprecated
-        public int computeVerticalScrollOffset(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
@@ -4658,15 +4426,6 @@
         }
 
         /**
-         * @deprecated
-         * Override computeVerticalScrollRange(State state) instead.
-         */
-        @Deprecated
-        public int computeVerticalScrollRange(Adapter adapter) {
-            return 0;
-        }
-
-        /**
          * <p>Override this method if you want to support scroll bars.</p>
          *
          * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
@@ -4690,10 +4449,12 @@
          * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
          * will consume all available space.</p>
          *
+         * @param recycler Recycler
+         * @param state Transient state of RecyclerView
          * @param widthSpec Width {@link android.view.View.MeasureSpec}
          * @param heightSpec Height {@link android.view.View.MeasureSpec}
          */
-        public void onMeasure(int widthSpec, int heightSpec) {
+        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
             final int widthMode = MeasureSpec.getMode(widthSpec);
             final int heightMode = MeasureSpec.getMode(heightSpec);
             final int widthSize = MeasureSpec.getSize(widthSpec);
@@ -4781,6 +4542,12 @@
                 mSmoothScroller = null;
             }
         }
+
+        void removeAndRecycleAllViews(Recycler recycler) {
+            for (int i = getChildCount() - 1; i >= 0; i--) {
+                removeAndRecycleViewAt(i, recycler);
+            }
+        }
     }
 
     /**
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 56b8b79..3c66d71 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -26,6 +26,7 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 abstract public class BaseRecyclerViewInstrumentationTest extends
         ActivityInstrumentationTestCase2<TestActivity> {
@@ -88,6 +89,34 @@
         });
     }
 
+    void scrollToPosition(final int position) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getLayoutManager().scrollToPosition(position);
+            }
+        });
+    }
+
+    void smoothScrollToPosition(final int position)
+            throws Throwable {
+        Log.d(TAG, "SMOOTH scrolling to " + position);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.smoothScrollToPosition(position);
+            }
+        });
+        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
+                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            if (mDebug) {
+                Log.d(TAG, "SMOOTH scrolling step");
+            }
+            Thread.sleep(200);
+        }
+        Log.d(TAG, "SMOOTH scrolling done");
+    }
+
     class TestViewHolder extends RecyclerView.ViewHolder {
 
         Item mBindedItem;
@@ -174,6 +203,8 @@
     }
 
     static class Item {
+        final static AtomicInteger idCounter = new AtomicInteger(0);
+        final public int mId = idCounter.incrementAndGet();
 
         int originalIndex;
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
new file mode 100644
index 0000000..e000bc9
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -0,0 +1,657 @@
+/*
+ * 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.v7.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Includes tests for {@link LinearLayoutManager}.
+ * <p>
+ * Since most UI tests are not practical, these tests are focused on internal data representation
+ * and stability of LinearLayoutManager in response to different events (state change, scrolling
+ * etc) where it is very hard to do manual testing.
+ */
+public class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "LinearLayoutManagerTest";
+
+    WrappedLinearLayoutManager mLayoutManager;
+
+    TestAdapter mTestAdapter;
+
+    final List<Config> mBaseVariations = new ArrayList<Config>();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        for (int orientation : new int[]{LinearLayoutManager.VERTICAL,
+                LinearLayoutManager.HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (boolean stackFromBottom : new boolean[]{false, true}) {
+                    mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
+                }
+            }
+        }
+    }
+
+    protected List<Config> addConfigVariation(List<Config> base, String fieldName,
+            Object... variations)
+            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
+        List<Config> newConfigs = new ArrayList<Config>();
+        Field field = Config.class.getDeclaredField(fieldName);
+        for (Config config : base) {
+            for (Object variation : variations) {
+                Config newConfig = (Config) config.clone();
+                field.set(newConfig, variation);
+                newConfigs.add(newConfig);
+            }
+        }
+        return newConfigs;
+    }
+
+    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
+        mRecyclerView = new RecyclerView(getActivity());
+        mRecyclerView.setHasFixedSize(true);
+        mTestAdapter = new TestAdapter(config.mItemCount);
+        mRecyclerView.setAdapter(mTestAdapter);
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
+                config.mReverseLayout);
+        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        if (waitForFirstLayout) {
+            waitForFirstLayout();
+        }
+    }
+
+    private void waitForFirstLayout() throws Throwable {
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(mRecyclerView);
+        mLayoutManager.waitForLayout(2);
+    }
+
+
+    public void testGetFirstLastChildrenTest() throws Throwable {
+        for (Config config : mBaseVariations) {
+            getFirstLastChildrenTest(config);
+        }
+    }
+
+    public void getFirstLastChildrenTest(final Config config) throws Throwable {
+        setupByConfig(config, true);
+        Runnable viewInBoundsTest = new Runnable() {
+            @Override
+            public void run() {
+                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
+                final String boundsLog = mLayoutManager.getBoundsLog();
+                assertEquals(config + ":\nfirst visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.firstVisiblePosition,
+                        mLayoutManager.findFirstVisibleItemPosition()
+                );
+                assertEquals(
+                        config + ":\nfirst fully visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
+                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
+                );
+
+                assertEquals(config + ":\nlast visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.lastVisiblePosition,
+                        mLayoutManager.findLastVisibleItemPosition()
+                );
+                assertEquals(
+                        config + ":\nlast fully visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
+                        mLayoutManager.findLastCompletelyVisibleItemPosition()
+                );
+            }
+        };
+        runTestOnUiThread(viewInBoundsTest);
+        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+        // case
+        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.smoothScrollToPosition(scrollPosition);
+            }
+        });
+        while (mLayoutManager.isSmoothScrolling() ||
+                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            runTestOnUiThread(viewInBoundsTest);
+            Thread.sleep(200);
+        }
+        // delete all items
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
+        mLayoutManager.waitForLayout(2);
+        // test empty case
+        runTestOnUiThread(viewInBoundsTest);
+        // set a new adapter with huge items to test full bounds check
+        mLayoutManager.expectLayouts(1);
+        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
+        final TestAdapter newAdapter = new TestAdapter(100) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (config.mOrientation == LinearLayoutManager.HORIZONTAL) {
+                    holder.itemView.setMinimumWidth(totalSpace + 5);
+                } else {
+                    holder.itemView.setMinimumHeight(totalSpace + 5);
+                }
+            }
+        };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setAdapter(newAdapter);
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        runTestOnUiThread(viewInBoundsTest);
+    }
+
+    public void testSavedState() throws Throwable {
+        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        // do nothing
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "doing nothing";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
+                                50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position with positive offset";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
+                                -50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position with negative offset";
+                    }
+                }
+        };
+
+        PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
+                new PostRestoreRunnable() {
+                    @Override
+                    public String describe() {
+                        return "Doing nothing";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        // update config as well so that restore assertions will work
+                        config.mOrientation = 1 - config.mOrientation;
+                        mLayoutManager.setOrientation(config.mOrientation);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return config.mItemCount == 0;
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing orientation";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        config.mStackFromEnd = !config.mStackFromEnd;
+                        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return true; //stack from end should not move items on change
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing stack from end";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        config.mReverseLayout = !config.mReverseLayout;
+                        mLayoutManager.setReverseLayout(config.mReverseLayout);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return config.mItemCount == 0;
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing reverse layout";
+                    }
+                }
+        };
+        boolean[] waitForLayoutOptions = new boolean[]{false, true};
+        for (Config config : addConfigVariation(mBaseVariations, "mItemCount", 0, 300)) {
+            for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
+                for (boolean waitForLayout : waitForLayoutOptions) {
+                    for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
+                        savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
+                                postRestoreRunnable);
+                        removeRecyclerView();
+                    }
+
+                }
+            }
+        }
+    }
+
+    public void savedStateTest(Config config, boolean waitForLayout,
+            PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
+            throws Throwable {
+        if (DEBUG) {
+            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
+                    config + " post layout action " + postLayoutOperation.describe() +
+                    "post restore action " + postRestoreOperation.describe());
+        }
+        setupByConfig(config, false);
+        if (waitForLayout) {
+            waitForFirstLayout();
+            postLayoutOperation.run();
+        }
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+        removeRecyclerView();
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        removeRecyclerView();
+
+        RecyclerView restored = new RecyclerView(getActivity());
+        // this config should be no op.
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
+                1 - config.mOrientation, !config.mReverseLayout);
+        mLayoutManager.setStackFromEnd(!config.mStackFromEnd);
+        restored.setLayoutManager(mLayoutManager);
+        // use the same adapter for Rect matching
+        restored.setAdapter(mTestAdapter);
+        restored.onRestoreInstanceState(savedState);
+        postRestoreOperation.onAfterRestore(config);
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(restored);
+        mLayoutManager.waitForLayout(2);
+        // calculate prefix here instead of above to include post restore changes
+        final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
+                "\npostRestore:" + postRestoreOperation.describe() + "\n";
+        assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
+                config.mReverseLayout, mLayoutManager.getReverseLayout());
+        assertEquals(logPrefix + " on saved state, orientation should be preserved",
+                config.mOrientation, mLayoutManager.getOrientation());
+        assertEquals(logPrefix + " on saved state, stack from end should be preserved",
+                config.mStackFromEnd, mLayoutManager.getStackFromEnd());
+        if (waitForLayout) {
+            if (postRestoreOperation.shouldLayoutMatch(config)) {
+                assertRectSetsEqual(
+                        logPrefix + ": on restore, previous view positions should be preserved",
+                        before, mLayoutManager.collectChildCoordinates());
+            } else {
+                assertRectSetsNotEqual(
+                        logPrefix
+                                + ": on restore with changes, previous view positions should NOT be preserved",
+                        before, mLayoutManager.collectChildCoordinates());
+            }
+        }
+    }
+
+    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLayoutManager.scrollToPositionWithOffset(position, offset);
+            }
+        });
+    }
+
+    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
+            Map<Item, Rect> after) {
+        Throwable throwable = null;
+        try {
+            assertRectSetsEqual("NOT " + message, before, after);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull(message + "\ntwo layout should be different", throwable);
+    }
+
+    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
+        if (DEBUG) {
+            Log.d(TAG, "checking rectangle equality.");
+            Log.d(TAG, "before:");
+            for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+                Log.d(TAG, entry.getKey().originalIndex + ":" + entry.getValue());
+            }
+            Log.d(TAG, "after:");
+            for (Map.Entry<Item, Rect> entry : after.entrySet()) {
+                Log.d(TAG, entry.getKey().originalIndex + ":" + entry.getValue());
+            }
+        }
+        assertEquals(message + ":\nitem counts should be equal", before.size()
+                , after.size());
+        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+            Rect afterRect = after.get(entry.getKey());
+            assertNotNull(message + ":\nSame item should be visible after simple re-layout",
+                    afterRect);
+            assertEquals(message + ":\nItem should be laid out at the same coordinates",
+                    entry.getValue(), afterRect);
+        }
+    }
+
+    static class VisibleChildren {
+
+        int firstVisiblePosition = RecyclerView.NO_POSITION;
+
+        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+        int lastVisiblePosition = RecyclerView.NO_POSITION;
+
+        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+        @Override
+        public String toString() {
+            return "VisibleChildren{" +
+                    "firstVisiblePosition=" + firstVisiblePosition +
+                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
+                    ", lastVisiblePosition=" + lastVisiblePosition +
+                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
+                    '}';
+        }
+    }
+
+    abstract private class PostLayoutRunnable {
+
+        abstract void run() throws Throwable;
+
+        abstract String describe();
+    }
+
+    abstract private class PostRestoreRunnable {
+
+        void onAfterRestore(Config config) throws Throwable {
+        }
+
+        abstract String describe();
+
+        boolean shouldLayoutMatch(Config config) {
+            return true;
+        }
+    }
+
+    class WrappedLinearLayoutManager extends LinearLayoutManager {
+
+        CountDownLatch layoutLatch;
+
+        OrientationHelper mSecondaryOrientation;
+
+        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+            super(context, orientation, reverseLayout);
+        }
+
+        public void expectLayouts(int count) {
+            layoutLatch = new CountDownLatch(count);
+        }
+
+        public void waitForLayout(long timeout) throws InterruptedException {
+            waitForLayout(timeout, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void setOrientation(int orientation) {
+            super.setOrientation(orientation);
+            mSecondaryOrientation = null;
+        }
+
+        @Override
+        void ensureRenderState() {
+            super.ensureRenderState();
+            if (mSecondaryOrientation == null) {
+                mSecondaryOrientation = getOrientation() == HORIZONTAL
+                        ? createVerticalOrientationHelper()
+                        : createHorizontalOrientationHelper();
+            }
+        }
+
+        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
+            layoutLatch.await(timeout, timeUnit);
+            assertEquals("all expected layouts should be executed at the expected time",
+                    0, layoutLatch.getCount());
+        }
+
+        public String getBoundsLog() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
+                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
+            sb.append("\nchildren bounds\n");
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
+                        .append("[").append("start:").append(
+                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
+                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
+            }
+            return sb.toString();
+        }
+
+        public VisibleChildren traverseAndFindVisibleChildren() {
+            int childCount = getChildCount();
+            final VisibleChildren visibleChildren = new VisibleChildren();
+            final int start = mOrientationHelper.getStartAfterPadding();
+            final int end = mOrientationHelper.getEndAfterPadding();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                final int childStart = mOrientationHelper.getDecoratedStart(child);
+                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+                final boolean fullyVisible = childStart >= start && childEnd <= end;
+                final boolean hidden = childEnd <= start || childStart >= end;
+                if (hidden) {
+                    continue;
+                }
+                final int position = getPosition(child);
+                if (fullyVisible) {
+                    if (position < visibleChildren.firstFullyVisiblePosition ||
+                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
+                        visibleChildren.firstFullyVisiblePosition = position;
+                    }
+
+                    if (position > visibleChildren.lastFullyVisiblePosition) {
+                        visibleChildren.lastFullyVisiblePosition = position;
+                    }
+                }
+
+                if (position < visibleChildren.firstVisiblePosition ||
+                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
+                    visibleChildren.firstVisiblePosition = position;
+                }
+
+                if (position > visibleChildren.lastVisiblePosition) {
+                    visibleChildren.lastVisiblePosition = position;
+                }
+
+            }
+            return visibleChildren;
+        }
+
+        Rect getViewBounds(View view) {
+            if (getOrientation() == HORIZONTAL) {
+                return new Rect(
+                        mOrientationHelper.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mOrientationHelper.getDecoratedEnd(view),
+                        mSecondaryOrientation.getDecoratedEnd(view));
+            } else {
+                return new Rect(
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mOrientationHelper.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedEnd(view),
+                        mOrientationHelper.getDecoratedEnd(view));
+            }
+
+        }
+
+        Map<Item, Rect> collectChildCoordinates() throws Throwable {
+            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    final int childCount = getChildCount();
+                    for (int i = 0; i < childCount; i++) {
+                        View child = getChildAt(i);
+                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
+                                .getLayoutParams();
+                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
+                        items.put(vh.mBindedItem, getViewBounds(child));
+                    }
+                }
+            });
+            return items;
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            super.onLayoutChildren(recycler, state);
+            layoutLatch.countDown();
+        }
+    }
+
+    static class Config implements Cloneable {
+
+        private static final int DEFAULT_ITEM_COUNT = 300;
+
+        private boolean mStackFromEnd;
+
+        int mOrientation = LinearLayoutManager.VERTICAL;
+
+        boolean mReverseLayout = false;
+
+        int mItemCount = DEFAULT_ITEM_COUNT;
+
+        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
+            mOrientation = orientation;
+            mReverseLayout = reverseLayout;
+            mStackFromEnd = stackFromEnd;
+        }
+
+        public Config() {
+
+        }
+
+        Config orientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        Config stackFromBottom(boolean stackFromBottom) {
+            mStackFromEnd = stackFromBottom;
+            return this;
+        }
+
+        Config reverseLayout(boolean reverseLayout) {
+            mReverseLayout = reverseLayout;
+            return this;
+        }
+
+        public Config itemCount(int itemCount) {
+            mItemCount = itemCount;
+            return this;
+        }
+
+        // required by convention
+        @Override
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+
+        @Override
+        public String toString() {
+            return "Config{" +
+                    "mStackFromEnd=" + mStackFromEnd +
+                    ", mOrientation=" + mOrientation +
+                    ", mReverseLayout=" + mReverseLayout +
+                    ", mItemCount=" + mItemCount +
+                    '}';
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 7d43610..210dd2b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -17,11 +17,19 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class RecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
 
@@ -56,9 +64,19 @@
 
     RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
             throws Throwable {
-        final RecyclerView recyclerView = new TestRecyclerView(getActivity());
+        return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
+    }
+
+    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
+            TestAdapter testAdapter)
+            throws Throwable {
+        final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
         recyclerView.setHasFixedSize(true);
-        mTestAdapter = new TestAdapter(itemCount);
+        if (testAdapter == null) {
+            mTestAdapter = new TestAdapter(itemCount);
+        } else {
+            mTestAdapter = testAdapter;
+        }
         recyclerView.setAdapter(mTestAdapter);
         mLayoutManager = new AnimationLayoutManager();
         recyclerView.setLayoutManager(mLayoutManager);
@@ -66,11 +84,108 @@
         mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
 
         mLayoutManager.expectLayouts(1);
+        recyclerView.expectDraw(1);
         setRecyclerView(recyclerView);
         mLayoutManager.waitForLayout(2);
+        recyclerView.waitForDraw(1);
+        mLayoutManager.mOnLayoutCallbacks.reset();
         return recyclerView;
     }
 
+
+    public void getItemForDeletedViewTest() throws Throwable {
+        testGetItemForDeletedView(false);
+        testGetItemForDeletedView(true);
+    }
+
+    public void testGetItemForDeletedView(boolean stableIds) throws Throwable {
+        final Set<Integer> itemViewTypeQueries = new HashSet<Integer>();
+        final Set<Integer> itemIdQueries = new HashSet<Integer>();
+        TestAdapter adapter = new TestAdapter(10) {
+            @Override
+            public int getItemViewType(int position) {
+                itemViewTypeQueries.add(position);
+                return super.getItemViewType(position);
+            }
+
+            @Override
+            public long getItemId(int position) {
+                itemIdQueries.add(position);
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(stableIds);
+        setupBasic(10, 0, 10, adapter);
+        assertEquals("getItemViewType for all items should be called", 10,
+                itemViewTypeQueries.size());
+        if (adapter.hasStableIds()) {
+            assertEquals("getItemId should be called when adapter has stable ids", 10,
+                    itemIdQueries.size());
+        } else {
+            assertEquals("getItemId should not be called when adapter does not have stable ids", 0,
+                    itemIdQueries.size());
+        }
+        itemViewTypeQueries.clear();
+        itemIdQueries.clear();
+        mLayoutManager.expectLayouts(2);
+        // delete last two
+        final int deleteStart = 8;
+        final int deleteCount = adapter.getItemCount() - deleteStart;
+        adapter.deleteAndNotify(deleteStart, deleteCount);
+        mLayoutManager.waitForLayout(2);
+        for (int i = 0; i < deleteStart; i++) {
+            assertTrue("getItemViewType for existing item " + i + " should be called",
+                    itemViewTypeQueries.contains(i));
+            if (adapter.hasStableIds()) {
+                assertTrue("getItemId for existing item " + i
+                        + " should be called when adapter has stable ids",
+                        itemIdQueries.contains(i));
+            }
+        }
+        for (int i = deleteStart; i < deleteStart + deleteCount; i++) {
+            assertFalse("getItemViewType for deleted item " + i + " SHOULD NOT be called",
+                    itemViewTypeQueries.contains(i));
+            if (adapter.hasStableIds()) {
+                assertTrue("getItemId for deleted item " + i + " SHOULD NOT be called",
+                        itemIdQueries.contains(i));
+            }
+        }
+    }
+
+    public void testAdapterChangeDuringScrolling() throws Throwable {
+        setupBasic(10);
+        final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
+        final AtomicInteger onScrollItemCount = new AtomicInteger(0);
+
+        mLayoutManager.setOnLayoutCallbacks(new OnLayoutCallbacks() {
+            @Override
+            void onLayoutChildren(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager lm, RecyclerView.State state) {
+                onLayoutItemCount.set(state.getItemCount());
+                super.onLayoutChildren(recycler, lm, state);
+            }
+
+            @Override
+            public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+                onScrollItemCount.set(state.getItemCount());
+                super.onScroll(dx, recycler, state);
+            }
+        });
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.mItems.remove(5);
+                mTestAdapter.notifyItemRangeRemoved(5, 1);
+                mRecyclerView.scrollBy(0, 100);
+                assertTrue("scrolling while there are pending adapter updates should "
+                        + "trigger a layout", mLayoutManager.mOnLayoutCallbacks.mLayoutCount > 0);
+                assertEquals("scroll by should be called w/ updated adapter count",
+                        mTestAdapter.mItems.size(), onScrollItemCount.get());
+
+            }
+        });
+    }
+
     public void testAddInvisibleAndVisible() throws Throwable {
         setupBasic(10, 1, 7);
         mLayoutManager.expectLayouts(2);
@@ -97,6 +212,26 @@
         mLayoutManager.waitForLayout(2);
     }
 
+    public TestRecyclerView getTestRecyclerView() {
+        return (TestRecyclerView) mRecyclerView;
+    }
+
+    public void testRemoveScrapInvalidate() throws Throwable {
+        setupBasic(10);
+        TestRecyclerView testRecyclerView = getTestRecyclerView();
+        mLayoutManager.expectLayouts(1);
+        testRecyclerView.expectDraw(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.mItems.clear();
+                mTestAdapter.notifyDataSetChanged();
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        testRecyclerView.waitForDraw(2);
+    }
+
     public void testDeleteVisibleAndInvisible() throws Throwable {
         setupBasic(11, 3, 5); //layout items  3 4 5 6 7
         mLayoutManager.expectLayouts(2);
@@ -185,6 +320,17 @@
             }
         }
 
+        @Override
+        public boolean canScrollVertically() {
+            return true;
+        }
+
+        @Override
+        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            mOnLayoutCallbacks.onScroll(dy, recycler, state);
+            return super.scrollVerticallyBy(dy, recycler, state);
+        }
 
         public void onPostDispatchLayout() {
             mOnLayoutCallbacks.postDispatchLayout();
@@ -295,10 +441,16 @@
         boolean inPreLayout() {
             return mLayoutCount == 0;
         }
+
+        public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+
+        }
     }
 
     class TestRecyclerView extends RecyclerView {
 
+        CountDownLatch drawLatch;
+
         public TestRecyclerView(Context context) {
             super(context);
         }
@@ -311,6 +463,24 @@
             super(context, attrs, defStyle);
         }
 
+        public void expectDraw(int count) {
+            drawLatch = new CountDownLatch(count);
+        }
+
+        public void waitForDraw(long timeout) throws Throwable {
+            drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
+            assertEquals("all expected draws should happen at the expected time frame",
+                    0, drawLatch.getCount());
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            super.dispatchDraw(canvas);
+            if (drawLatch != null) {
+                drawLatch.countDown();
+            }
+        }
+
         @Override
         void dispatchLayout() {
             try {
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 d218f45..d68391e 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -199,8 +199,7 @@
         }
 
         @Override
-        public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
-                boolean structureChanged, RecyclerView.State state) {
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
             mLayoutCount += 1;
         }