am f9fd9749: am 26d0f7a7: Merge "Remove unused LOCAL_LDLIBS."

* commit 'f9fd97499795cd47473f0344e00db9c9837eea36':
  Remove unused LOCAL_LDLIBS.
diff --git a/CleanSpec.mk b/CleanSpec.mk
index ea4358f..317bb03 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -48,6 +48,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index 3232ff2..ce49b6e 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -29,7 +29,7 @@
 /**
  * Denotes that the annotated element of integer type, represents
  * a logical type and that its value should be one of the explicitly
- * named constants. If the {@link #flag()} attribute is set to true,
+ * named constants. If the IntDef#flag() attribute is set to true,
  * multiple constants can be combined.
  * <p>
  * Example:
diff --git a/build.gradle b/build.gradle
index a95df22..ae2f294 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,12 +5,12 @@
         maven { url '../../prebuilts/tools/common/m2/internal' }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:0.9.+'
+        classpath 'com.android.tools.build:gradle:0.10.0'
     }
 }
 
-ext.supportVersion = '19.1.0'
-ext.extraVersion = 5
+ext.supportVersion = '19.2.0'
+ext.extraVersion = 6
 ext.supportRepoOut = ''
 
 /*
diff --git a/settings.gradle b/settings.gradle
index 83498d3..2104257 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,6 @@
+include ':support-annotations'
+project(':support-annotations').projectDir = new File(rootDir, 'annotations')
+
 include ':support-v4'
 project(':support-v4').projectDir = new File(rootDir, 'v4')
 
@@ -10,8 +13,8 @@
 include ':support-mediarouter-v7'
 project(':support-mediarouter-v7').projectDir = new File(rootDir, 'v7/mediarouter')
 
+include ':support-recyclerview-v7'
+project(':support-recyclerview-v7').projectDir = new File(rootDir, 'v7/recyclerview')
+
 include ':support-v13'
 project(':support-v13').projectDir = new File(rootDir, 'v13')
-
-include ':support-annotations'
-project(':support-annotations').projectDir = new File(rootDir, 'annotations')
diff --git a/v17/leanback/.classpath b/v17/leanback/.classpath
new file mode 100644
index 0000000..a4763d1
--- /dev/null
+++ b/v17/leanback/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<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 kind="output" path="bin/classes"/>
+</classpath>
diff --git a/v17/leanback/.gitignore b/v17/leanback/.gitignore
new file mode 100644
index 0000000..c3c25e0
--- /dev/null
+++ b/v17/leanback/.gitignore
@@ -0,0 +1,4 @@
+.settings
+bin
+libs
+gen
diff --git a/v17/leanback/.project b/v17/leanback/.project
new file mode 100644
index 0000000..9d191cd
--- /dev/null
+++ b/v17/leanback/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>android-support-v17-leanback</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
new file mode 100644
index 0000000..01a8ae3
--- /dev/null
+++ b/v17/leanback/Android.mk
@@ -0,0 +1,144 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build the resources using the current SDK version.
+# We do this here because the final static library must be compiled with an older
+# SDK version than the resources.  The resources library and the R class that it
+# contains will not be linked into the final static library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-res
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_AAPT_FLAGS := \
+        --auto-add-overlay
+LOCAL_JAR_EXCLUDE_FILES := none
+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
+LOCAL_SDK_VERSION := 19
+LOCAL_SRC_FILES := $(call all-java-files-under, kitkat)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+#  A helper sub-library that makes direct use of JBMR2 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-jbmr2
+LOCAL_SDK_VERSION := 19
+LOCAL_SRC_FILES := $(call all-java-files-under, jbmr2)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback
+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
+LOCAL_JAVA_LIBRARIES := \
+        android-support-v4 \
+        android-support-v7-recyclerview \
+        android-support-v17-leanback-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+
+# ===========================================================
+# Common Droiddoc vars
+leanback.docs.src_files := \
+    $(call all-java-files-under, src) \
+    $(call all-html-files-under, src)
+leanback.docs.java_libraries := \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback-res \
+    android-support-v17-leanback
+
+# Documentation
+# ===========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-support-v17-leanback
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+
+intermediates.COMMON := $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),android-support-v17-leanback,,COMMON)
+
+LOCAL_SRC_FILES := $(leanback.docs.src_files)
+LOCAL_ADDITONAL_JAVA_DIR := $(intermediates.COMMON)/src
+
+LOCAL_SDK_VERSION := 19
+LOCAL_IS_HOST_MODULE := false
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
+
+LOCAL_JAVA_LIBRARIES := $(leanback.docs.java_libraries)
+
+LOCAL_DROIDDOC_OPTIONS := \
+    -offlinemode \
+    -hdf android.whichdoc offline \
+    -federate Android http://developer.android.com \
+    -federationapi Android prebuilts/sdk/api/17.txt \
+    -hide 113
+
+include $(BUILD_DROIDDOC)
+
+# Stub source files
+# ===========================================================
+
+leanback_internal_api_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/android-support-v17-leanback_api.txt
+leanback.docs.stubpackages := android.support.v17.leanback:android.support.v17.leanback.app:android.support.v17.leanback.database:android.support.v17.leanback.widget
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-support-v17-leanback-stubs
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(leanback.docs.src_files)
+LOCAL_JAVA_LIBRARIES := $(leanback.docs.java_libraries)
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
+LOCAL_UNINSTALLABLE_MODULE := true
+
+LOCAL_DROIDDOC_OPTIONS := \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android-support-v17-leanback-stubs_intermediates/src \
+    -stubpackages $(leanback.docs.stubpackages) \
+    -api $(leanback_internal_api_file) \
+    -hide 113 \
+    -nodocs
+
+include $(BUILD_DROIDDOC)
+leanback_stubs_stamp := $(full_target)
+$(leanback_internal_api_file) : $(full_target)
+
+# Cleanup temp vars
+# ===========================================================
+leanback.docs.src_files :=
+leanback.docs.java_libraries :=
+intermediates.COMMON :=
+leanback_internal_api_file :=
+leanback_stubs_stamp :=
+leanback.docs.stubpackages :=
diff --git a/v17/leanback/AndroidManifest.xml b/v17/leanback/AndroidManifest.xml
new file mode 100644
index 0000000..20ef094
--- /dev/null
+++ b/v17/leanback/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v17.leanback">
+    <uses-sdk android:minSdkVersion="17"/>
+    <application />
+</manifest>
diff --git a/v17/leanback/README.txt b/v17/leanback/README.txt
new file mode 100644
index 0000000..f3dbe92
--- /dev/null
+++ b/v17/leanback/README.txt
@@ -0,0 +1 @@
+Library Project including Leanback framework support.
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
new file mode 100644
index 0000000..89d5f3b
--- /dev/null
+++ b/v17/leanback/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'support-leanback-v17'
+
+dependencies {
+    compile project(':support-v4')
+    compile project(':support-recyclerview-v7')
+}
+
+android {
+    compileSdkVersion 'current'
+    buildToolsVersion "19.0.1"
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = [
+            'src',
+            'kitkat',
+            'jbmr2'
+        ]
+        main.res.srcDir 'res'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java b/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
new file mode 100644
index 0000000..ad53425
--- /dev/null
+++ b/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
@@ -0,0 +1,50 @@
+/*
+ * 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+class ShadowHelperJbmr2 {
+
+    static class ShadowImpl {
+        View mNormalShadow;
+        View mFocusShadow;
+    }
+
+    /* prepare parent for allowing shadows of a child */
+    public static void prepareParent(ViewGroup parent) {
+        parent.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+    }
+
+    /* add shadows and return a implementation detail object */
+    public static Object addShadow(ViewGroup shadowContainer) {
+        shadowContainer.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+        LayoutInflater inflater = LayoutInflater.from(shadowContainer.getContext());
+        inflater.inflate(R.layout.lb_shadow, shadowContainer, true);
+        ShadowImpl impl = new ShadowImpl();
+        impl.mNormalShadow = shadowContainer.findViewById(R.id.lb_shadow_normal);
+        impl.mFocusShadow = shadowContainer.findViewById(R.id.lb_shadow_focused);
+        return impl;
+    }
+
+    /* set shadow focus level 0 for unfocused 1 for fully focused */
+    public static void setShadowFocusLevel(Object impl, float level) {
+        ShadowImpl shadowImpl = (ShadowImpl) impl;
+        shadowImpl.mNormalShadow.setAlpha(1 - level);
+        shadowImpl.mFocusShadow.setAlpha(level);
+    }
+}
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/app/ChangeBoundsKitKat.java b/v17/leanback/kitkat/android/support/v17/leanback/app/ChangeBoundsKitKat.java
new file mode 100644
index 0000000..ce8c2de
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/app/ChangeBoundsKitKat.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.transition.ChangeBounds;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+
+import java.util.Map;
+
+/**
+ * This is a replacement of android.transition.ChangeBounds that treat reparent
+ * views slightly differently:  see "PATCH" in the code.
+ */
+class ChangeBoundsKitKat extends ChangeBounds {
+
+    private static final String PROPNAME_PARENT = "android:changeBounds:parent";
+    private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
+    private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
+
+    int[] tempLocation = new int[2];
+    boolean mReparent = false;
+    private static final String LOG_TAG = "ChangeBoundsKitKat";
+
+    private static RectEvaluator sRectEvaluator = new RectEvaluator();
+
+    @Override
+    public void setReparent(boolean reparent) {
+        super.setReparent(reparent);
+        mReparent = reparent;
+    }
+
+    @Override
+    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null) {
+            return null;
+        }
+        Map<String, Object> startParentVals = startValues.values;
+        Map<String, Object> endParentVals = endValues.values;
+        ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
+        ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
+        if (startParent == null || endParent == null) {
+            return null;
+        }
+        final View view = endValues.view;
+        boolean parentsEqual = (startParent == endParent) ||
+                (startParent.getId() == endParent.getId());
+        // TODO: Might want reparenting to be separate/subclass transition, or at least
+        // triggered by a property on ChangeBounds. Otherwise, we're forcing the requirement that
+        // all parents in layouts have IDs to avoid layout-inflation resulting in a side-effect
+        // of reparenting the views.
+        if (!mReparent || parentsEqual) {
+            return super.createAnimator(sceneRoot, startValues, endValues);
+        } else {
+            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
+            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
+            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
+            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
+            // TODO: also handle size changes: check bounds and animate size changes
+            if (startX != endX || startY != endY) {
+                sceneRoot.getLocationInWindow(tempLocation);
+                Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(bitmap);
+                view.draw(canvas);
+                final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+                view.setVisibility(View.INVISIBLE);
+                sceneRoot.getOverlay().add(drawable);
+                Rect startBounds1 = new Rect(startX - tempLocation[0], startY - tempLocation[1],
+                        startX - tempLocation[0] + view.getWidth(),
+                        startY - tempLocation[1] + view.getHeight());
+                // PATCH : initialize the startBounds immediately so that the bitmap
+                // will show up immediately without waiting start delay.
+                drawable.setBounds(startBounds1);
+                Rect endBounds1 = new Rect(endX - tempLocation[0], endY - tempLocation[1],
+                        endX - tempLocation[0] + view.getWidth(),
+                        endY - tempLocation[1] + view.getHeight());
+                ObjectAnimator anim = ObjectAnimator.ofObject(drawable, "bounds",
+                        sRectEvaluator, startBounds1, endBounds1);
+                // PATCH : switch back to view when whole transition finishes.
+                TransitionListener transitionListener = new TransitionListener() {
+                    @Override
+                    public void onTransitionCancel(Transition transition) {
+                    }
+
+                    @Override
+                    public void onTransitionEnd(Transition transition) {
+                        sceneRoot.getOverlay().remove(drawable);
+                        view.setVisibility(View.VISIBLE);
+                        removeListener(this);
+                    }
+
+                    @Override
+                    public void onTransitionPause(Transition transition) {
+                    }
+
+                    @Override
+                    public void onTransitionResume(Transition transition) {
+                    }
+
+                    @Override
+                    public void onTransitionStart(Transition transition) {
+                    }
+                };
+                addListener(transitionListener);
+                return anim;
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/app/TransitionHelperKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/app/TransitionHelperKitkat.java
new file mode 100644
index 0000000..4ad20b0
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/app/TransitionHelperKitkat.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.transition.AutoTransition;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+class TransitionHelperKitkat {
+
+    private final Context mContext;
+
+    TransitionHelperKitkat(Context context) {
+        mContext = context;
+    }
+
+    Object createScene(ViewGroup sceneRoot, Runnable enterAction) {
+        Scene scene = new Scene(sceneRoot);
+        scene.setEnterAction(enterAction);
+        return scene;
+    }
+
+    Object createTransitionSet(boolean sequential) {
+        TransitionSet set = new TransitionSet();
+        set.setOrdering(sequential ? TransitionSet.ORDERING_SEQUENTIAL :
+            TransitionSet.ORDERING_TOGETHER);
+        return set;
+    }
+
+    void addTransition(Object transitionSet, Object transition) {
+        ((TransitionSet) transitionSet).addTransition((Transition) transition);
+    }
+
+    Object createAutoTransition() {
+        return new AutoTransition();
+    }
+
+    Object createFadeTransition(int fadingMode) {
+        Fade fade = new Fade(fadingMode);
+        return fade;
+    }
+
+    /**
+     * change bounds that support customized start delay.
+     */
+    static class CustomChangeBounds extends ChangeBoundsKitKat {
+
+        int mDefaultStartDelay;
+        // View -> delay
+        final HashMap<View, Integer> mViewStartDelays = new HashMap<View, Integer>();
+        // id -> delay
+        final SparseIntArray mIdStartDelays = new SparseIntArray();
+        // Class.getName() -> delay
+        final HashMap<String, Integer> mClassStartDelays = new HashMap<String, Integer>();
+
+        private int getDelay(View view) {
+            Integer delay = mViewStartDelays.get(view);
+            if (delay != null) {
+                return delay;
+            }
+            int idStartDelay = mIdStartDelays.get(view.getId(), -1);
+            if (idStartDelay != -1) {
+                return idStartDelay;
+            }
+            delay = mClassStartDelays.get(view.getClass().getName());
+            if (delay != null) {
+                return delay;
+            }
+            return mDefaultStartDelay;
+        }
+
+        @Override
+        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
+            if (animator != null && endValues != null && endValues.view != null) {
+                animator.setStartDelay(getDelay(endValues.view));
+            }
+            return animator;
+        }
+
+        public void setStartDelay(View view, int startDelay) {
+            mViewStartDelays.put(view, startDelay);
+        }
+
+        public void setStartDelay(int viewId, int startDelay) {
+            mIdStartDelays.put(viewId, startDelay);
+        }
+
+        public void setStartDelay(String className, int startDelay) {
+            mClassStartDelays.put(className, startDelay);
+        }
+
+        public void setDefaultStartDelay(int startDelay) {
+            mDefaultStartDelay = startDelay;
+        }
+    }
+
+    Object createChangeBounds(boolean reparent) {
+        CustomChangeBounds changeBounds = new CustomChangeBounds();
+        changeBounds.setReparent(reparent);
+        return changeBounds;
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(viewId, startDelay);
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(view, startDelay);
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, String className, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(className, startDelay);
+    }
+
+    void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setDefaultStartDelay(startDelay);
+    }
+
+    void exclude(Object transition, int targetId, boolean exclude) {
+        ((Transition) transition).excludeTarget(targetId, exclude);
+    }
+
+    void exclude(Object transition, View targetView, boolean exclude) {
+        ((Transition) transition).excludeTarget(targetView, exclude);
+    }
+
+    void excludeChildren(Object transition, int targetId, boolean exclude) {
+        ((Transition) transition).excludeChildren(targetId, exclude);
+    }
+
+    void excludeChildren(Object transition, View targetView, boolean exclude) {
+        ((Transition) transition).excludeChildren(targetView, exclude);
+    }
+
+    void include(Object transition, int targetId) {
+        ((Transition) transition).addTarget(targetId);
+    }
+
+    void include(Object transition, View targetView) {
+        ((Transition) transition).addTarget(targetView);
+    }
+
+    public void setTransitionCompleteListener(Object transition, Runnable listener) {
+        Transition t = (Transition) transition;
+        final Runnable completeListener = listener;
+        t.addListener(new Transition.TransitionListener() {
+
+            @Override
+            public void onTransitionStart(Transition transition) {
+            }
+
+            @Override
+            public void onTransitionResume(Transition transition) {
+            }
+
+            @Override
+            public void onTransitionPause(Transition transition) {
+            }
+
+            @Override
+            public void onTransitionEnd(Transition transition) {
+                completeListener.run();
+            }
+
+            @Override
+            public void onTransitionCancel(Transition transition) {
+            }
+        });
+    }
+
+    void runTransition(Object scene, Object transition) {
+        TransitionManager.go((Scene) scene, (Transition) transition);
+    }
+}
diff --git a/v17/leanback/project.properties b/v17/leanback/project.properties
new file mode 100644
index 0000000..91d2b02
--- /dev/null
+++ b/v17/leanback/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library=true
diff --git a/v17/leanback/res/drawable-hdpi/ic_action_search.png b/v17/leanback/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..a70393b
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/ic_circle_g_bg.png b/v17/leanback/res/drawable-hdpi/ic_circle_g_bg.png
new file mode 100644
index 0000000..6777e19
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/ic_circle_g_bg.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/ic_circle_shadow.9.png b/v17/leanback/res/drawable-hdpi/ic_circle_shadow.9.png
new file mode 100644
index 0000000..f556eac
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/ic_circle_shadow.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..112b541
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..7c59b7f
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..4abb20a
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/ic_action_search.png b/v17/leanback/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..dea3962
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/ic_circle_g_bg.png b/v17/leanback/res/drawable-mdpi/ic_circle_g_bg.png
new file mode 100644
index 0000000..641f096
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/ic_circle_g_bg.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/ic_circle_shadow.9.png b/v17/leanback/res/drawable-mdpi/ic_circle_shadow.9.png
new file mode 100644
index 0000000..f45b76c
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/ic_circle_shadow.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..1d2b041
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..39b220c
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..b9c3400
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/ic_action_search.png b/v17/leanback/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..19658e4
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/ic_circle_g_bg.png b/v17/leanback/res/drawable-xhdpi/ic_circle_g_bg.png
new file mode 100644
index 0000000..acb7c79
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/ic_circle_g_bg.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/ic_circle_shadow.9.png b/v17/leanback/res/drawable-xhdpi/ic_circle_shadow.9.png
new file mode 100644
index 0000000..60930f4
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/ic_circle_shadow.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..5e7c2be
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..599928b
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..e5413a8
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
new file mode 100644
index 0000000..1364a48
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/ic_action_search.png b/v17/leanback/res/drawable-xxhdpi/ic_action_search.png
new file mode 100644
index 0000000..a108638
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/ic_action_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/ic_circle_g_bg.png b/v17/leanback/res/drawable-xxhdpi/ic_circle_g_bg.png
new file mode 100644
index 0000000..2bd8ab7
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/ic_circle_g_bg.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/ic_circle_shadow.9.png b/v17/leanback/res/drawable-xxhdpi/ic_circle_shadow.9.png
new file mode 100644
index 0000000..619ce81
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/ic_circle_shadow.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..48f82a4
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..125bf12
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..887d24f
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_action_bg.xml b/v17/leanback/res/drawable/lb_action_bg.xml
new file mode 100644
index 0000000..76fbd8f
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_action_bg.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="2dp" />
+    <solid android:color="@color/lb_action_bg_color" />
+</shape>
diff --git a/v17/leanback/res/drawable/lb_background.xml b/v17/leanback/res/drawable/lb_background.xml
new file mode 100644
index 0000000..4732bc1
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_theme"/>
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_color"/>
+    <!-- Replaced at runtime with image to fade out -->
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_imageout"/>
+    <!-- Replaced at runtime with image to fade in -->
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_imagein"/>
+    <item android:drawable="@color/lb_background_protection" android:id="@+id/background_dim" />
+</layer-list>
diff --git a/v17/leanback/res/drawable/lb_transition_action_bg.xml b/v17/leanback/res/drawable/lb_transition_action_bg.xml
new file mode 100644
index 0000000..36688bc
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_transition_action_bg.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<transition xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/transparent" />
+    <item android:drawable="@drawable/lb_action_bg_focused" />
+</transition>
diff --git a/v17/leanback/res/layout/lb_action_1_line.xml b/v17/leanback/res/layout/lb_action_1_line.xml
new file mode 100644
index 0000000..2374cf3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_action_1_line.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lb_action_text"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/lb_action_1_line_height"
+    android:background="@drawable/lb_transition_action_bg"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:gravity="center_vertical"
+    android:lines="1"
+    android:paddingLeft="@dimen/lb_action_1_line_padding_left"
+    android:paddingRight="@dimen/lb_action_padding_right"
+    android:textAllCaps="true"
+    android:textColor="@color/lb_action_text_color"
+    android:textSize="@dimen/lb_action_text_size" />
diff --git a/v17/leanback/res/layout/lb_action_2_lines.xml b/v17/leanback/res/layout/lb_action_2_lines.xml
new file mode 100644
index 0000000..15cff81
--- /dev/null
+++ b/v17/leanback/res/layout/lb_action_2_lines.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/lb_action_2_lines_height"
+    android:background="@drawable/lb_transition_action_bg"
+    android:focusable="true"
+    android:focusableInTouchMode="true" >
+
+    <ImageView
+        android:id="@+id/lb_action_icon"
+        android:layout_width="@dimen/lb_action_icon_width"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:layout_marginLeft="@dimen/lb_action_icon_margin"
+        android:layout_marginRight="@dimen/lb_action_icon_margin" />
+
+    <TextView
+        android:id="@+id/lb_action_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_toRightOf="@id/lb_action_icon"
+        android:gravity="left|center_vertical"
+        android:lines="2"
+        android:textAllCaps="true"
+        android:textColor="@color/lb_action_text_color"
+        android:textSize="@dimen/lb_action_text_size" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_background_window.xml b/v17/leanback/res/layout/lb_background_window.xml
new file mode 100644
index 0000000..73c84d2
--- /dev/null
+++ b/v17/leanback/res/layout/lb_background_window.xml
@@ -0,0 +1,21 @@
+<!--?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    />
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
new file mode 100644
index 0000000..0315daa
--- /dev/null
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<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" >
+
+    <!-- BrowseFrameLayout serves as root of transition and manages switch between
+         left and right-->
+    <android.support.v17.leanback.app.BrowseFrameLayout
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:id="@+id/browse_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+        <include layout="@layout/lb_browse_title" />
+        <android.support.v17.leanback.app.BrowseRowsFrameLayout
+            android:id="@+id/browse_container_dock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent" />
+        <FrameLayout
+            android:id="@+id/browse_headers_dock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent" />
+    </android.support.v17.leanback.app.BrowseFrameLayout>
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/v17/leanback/res/layout/lb_browse_title.xml
new file mode 100644
index 0000000..77bd35c
--- /dev/null
+++ b/v17/leanback/res/layout/lb_browse_title.xml
@@ -0,0 +1,56 @@
+<?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.
+-->
+<LinearLayout 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"
+    android:paddingTop="?attr/browsePaddingTop"
+    android:paddingLeft="?attr/browsePaddingLeft">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <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_marginRight="@dimen/lb_browse_title_icon_margin_right"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true"
+            android:src="@null"
+            android:visibility="gone"
+            style="?attr/browseTitleIconStyle"/>
+
+        <TextView
+            android:id="@+id/browse_title"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/lb_browse_title_height"
+            android:layout_toRightOf="@id/browse_badge"
+            android:layout_centerVertical="true"
+            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_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:layout_marginRight="48dip"
+            android:layout_marginBottom="4dip"/>
+    </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_card_color_overlay.xml
new file mode 100644
index 0000000..45a40e1
--- /dev/null
+++ b/v17/leanback/res/layout/lb_card_color_overlay.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<View
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent" />
diff --git a/v17/leanback/res/layout/lb_details_description.xml b/v17/leanback/res/layout/lb_details_description.xml
new file mode 100644
index 0000000..1cd26cd
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_description.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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    >
+
+    <TextView
+        android:id="@+id/lb_details_description_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionTitleStyle"
+        />
+
+    <TextView
+        android:id="@+id/lb_details_description_subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionSubtitleStyle"
+        />
+
+    <TextView
+        android:id="@+id/lb_details_description_body"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionBodyStyle"
+        />
+</LinearLayout>
diff --git a/v17/leanback/res/layout/lb_details_fragment.xml b/v17/leanback/res/layout/lb_details_fragment.xml
new file mode 100644
index 0000000..92cf4b4
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_fragment.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/dummy"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <FrameLayout
+        android:id="@+id/fragment_dock"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/v17/leanback/res/layout/lb_details_overview.xml
new file mode 100644
index 0000000..ea8220e
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_overview.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <ImageView
+            android:id="@+id/details_overview_image"
+            android:layout_width="@dimen/lb_details_overview_image_width"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/lb_details_overview_image_margin_left"
+            android:gravity="top|left" />
+
+        <FrameLayout
+            android:id="@+id/details_overview_description"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
+            android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
+            android:gravity="top" />
+    </LinearLayout>
+
+    <android.support.v17.leanback.widget.HorizontalGridView
+        android:id="@+id/details_overview_actions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/lb_details_overiew_actions_margin_top"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:paddingLeft="@dimen/lb_details_overview_actions_padding_left"
+        android:paddingRight="@dimen/lb_details_overview_actions_padding_right"
+        lb:horizontalMargin="@dimen/lb_details_overview_action_items_margin"
+        lb:rowHeight="@dimen/lb_details_overview_actions_height" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_headers_fragment.xml b/v17/leanback/res/layout/lb_headers_fragment.xml
new file mode 100644
index 0000000..c72cd06
--- /dev/null
+++ b/v17/leanback/res/layout/lb_headers_fragment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/browse_headers"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    style="?attr/headersVerticalGridStyle"/>
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
new file mode 100644
index 0000000..0a14ff7
--- /dev/null
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -0,0 +1,85 @@
+<?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"
+    xmlns:lb="http://schemas.android.com/apk/res-auto">
+
+    <ImageView
+        android:id="@+id/main_image"
+        lb:layout_viewType="main"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:scaleType="centerCrop"
+        android:contentDescription="@null" />
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        lb:layout_viewType="info" >
+        <RelativeLayout
+            android:id="@+id/info_field"
+            android:background="@color/lb_basic_card_info_bg_color"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/lb_basic_card_info_height"
+            android:padding="@dimen/lb_basic_card_info_padding"
+            android:layout_centerHorizontal="true" >
+            <TextView
+                android:id="@+id/title_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_marginTop="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:maxLines="1"
+                android:fontFamily="sans-serif-condensed"
+                android:textColor="@color/lb_basic_card_title_text_color"
+                android:textSize="@dimen/lb_basic_card_title_text_size"
+                android:ellipsize="end" />
+            <TextView
+                android:id="@+id/content_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentBottom="true"
+                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginBottom="@dimen/lb_basic_card_info_text_margin"
+                android:maxLines="1"
+                android:fontFamily="sans-serif-condensed"
+                android:textColor="@color/lb_basic_card_content_text_color"
+                android:textSize="@dimen/lb_basic_card_content_text_size"
+                android:ellipsize="none" />
+            <ImageView
+                android:id="@+id/extra_badge"
+                android:layout_width="@dimen/lb_basic_card_info_badge_size"
+                android:layout_height="@dimen/lb_basic_card_info_badge_size"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentRight="true"
+                android:scaleType="fitCenter"
+                android:background="@color/lb_basic_card_info_bg_color"
+                android:contentDescription="@null" />
+            <ImageView
+                android:id="@+id/fade_mask"
+                android:src="@drawable/lb_ic_card_info_text_fade"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/lb_basic_card_info_badge_size"
+                android:layout_alignParentBottom="true"
+                android:layout_toStartOf="@id/extra_badge"
+                android:scaleType="fitCenter"
+                android:contentDescription="@null" />
+        </RelativeLayout>
+    </FrameLayout>
+</merge>
diff --git a/v17/leanback/res/layout/lb_list_row.xml b/v17/leanback/res/layout/lb_list_row.xml
new file mode 100644
index 0000000..a432518
--- /dev/null
+++ b/v17/leanback/res/layout/lb_list_row.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<android.support.v17.leanback.widget.HorizontalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/row_content"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/lb_browse_row_list_height"
+    style="?attr/rowHorizontalGridStyle" />
diff --git a/v17/leanback/res/layout/lb_list_row_hovercard.xml b/v17/leanback/res/layout/lb_list_row_hovercard.xml
new file mode 100644
index 0000000..a001dc9
--- /dev/null
+++ b/v17/leanback/res/layout/lb_list_row_hovercard.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/hovercard_panel"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/rowHoverCardTitleStyle" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/rowHoverCardDescriptionStyle" />
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_container.xml b/v17/leanback/res/layout/lb_row_container.xml
new file mode 100644
index 0000000..0f5dd5f
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_container.xml
@@ -0,0 +1,26 @@
+<?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">
+<LinearLayout
+    android:id="@+id/lb_row_container_header_dock"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingLeft="?attr/browsePaddingLeft"
+    android:clipToPadding="false">
+</LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
new file mode 100644
index 0000000..5b147c5
--- /dev/null
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    style="?attr/rowsVerticalGridStyle" />
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
new file mode 100644
index 0000000..8e1a1b7
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -0,0 +1,65 @@
+<?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.
+-->
+<android.support.v17.leanback.widget.SearchBar
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/lb_search_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+
+    <RelativeLayout
+            android:id="@+id/lb_search_bar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/lb_search_bar_height"
+            android:paddingLeft="@dimen/lb_search_bar_padding_left"
+            android:clipChildren="false"
+            android:layout_alignParentTop="true"
+            android:layout_gravity="top"
+            android:background="@android:color/transparent" >
+
+
+        <FrameLayout
+                android:id="@+id/lb_search_bar_items"
+                android:layout_width="@dimen/lb_search_bar_items_width"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:layout_marginLeft="@dimen/lb_search_browse_row_padding_left"
+                android:layout_marginTop="@dimen/lb_search_bar_items_layout_margin_top"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentTop="true"
+                android:orientation="horizontal"
+                android:background="@android:color/transparent"
+                android:layout_weight="1">
+                <android.support.v17.leanback.widget.SearchEditText
+                        android:id="@+id/lb_search_text_editor"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:cursorVisible="true"
+                        android:editable="true"
+                        android:background="@null"
+                        android:fontFamily="sans-serif"
+                        android:focusable="true"
+                        android:imeOptions="normal|flagNoExtractUi|actionSearch"
+                        android:inputType="text|textAutoComplete"
+                        android:singleLine="true"
+                        android:textColor="@color/lb_search_bar_text_color"
+                        android:textColorHint="@color/lb_search_bar_hint_color"
+                        android:textCursorDrawable="@null"
+                        android:hint="@string/lb_search_bar_hint"
+                        android:textSize="@dimen/lb_search_bar_text_size"/>
+        </FrameLayout>
+    </RelativeLayout>
+</android.support.v17.leanback.widget.SearchBar>
diff --git a/v17/leanback/res/layout/lb_search_fragment.xml b/v17/leanback/res/layout/lb_search_fragment.xml
new file mode 100644
index 0000000..985ab21
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_fragment.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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/lb_search_frame"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:paddingTop="@dimen/lb_search_bar_padding_top">
+        <FrameLayout
+                android:id="@+id/lb_results_frame"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"/>
+        <include layout="@layout/lb_search_bar" />
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_search_orb.xml b/v17/leanback/res/layout/lb_search_orb.xml
new file mode 100644
index 0000000..c6bf690
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_orb.xml
@@ -0,0 +1,58 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="wrap_content"
+    android:orientation="horizontal"
+    android:focusable="true">
+
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:id="@+id/title"
+        android:text="@string/orb_search_label"
+        android:layout_gravity="center_vertical"
+        android:textAppearance="@style/TextAppearance.Leanback.SearchLabel" />
+
+    <FrameLayout
+        android:id="@+id/search_orb"
+        android:layout_width="@dimen/lb_search_orb_size"
+        android:layout_height="@dimen/lb_search_orb_size"
+        android:layout_gravity="top|start"
+        android:clipChildren="false"
+        android:layout_marginBottom="@dimen/lb_search_orb_margin_bottom"
+        android:layout_marginLeft="@dimen/lb_search_orb_margin_left"
+        android:layout_marginRight="@dimen/lb_search_orb_margin_right"
+        android:layout_marginTop="@dimen/lb_search_orb_margin_top" >
+
+        <ImageView
+            android:id="@+id/orb"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:src="@drawable/ic_circle_g_bg"
+            android:background="@drawable/ic_circle_shadow"
+            android:contentDescription="@null" />
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:src="@drawable/ic_action_search"
+            android:contentDescription="@string/orb_search_action" />
+    </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_shadow.xml b/v17/leanback/res/layout/lb_shadow.xml
new file mode 100644
index 0000000..b0aa0b1
--- /dev/null
+++ b/v17/leanback/res/layout/lb_shadow.xml
@@ -0,0 +1,31 @@
+<?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">
+
+    <View
+        android:id="@+id/lb_shadow_normal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/lb_card_shadow_normal" />
+    <View
+        android:id="@+id/lb_shadow_focused"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/lb_card_shadow_focused"
+        android:alpha="0" />
+
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_vertical_grid.xml b/v17/leanback/res/layout/lb_vertical_grid.xml
new file mode 100644
index 0000000..5dfe0c9
--- /dev/null
+++ b/v17/leanback/res/layout/lb_vertical_grid.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_grid"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    style="?attr/itemsVerticalGridStyle" />
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
new file mode 100644
index 0000000..902c483
--- /dev/null
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/lb_browse_title" />
+
+    <FrameLayout
+        android:id="@+id/browse_grid_dock"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
new file mode 100644
index 0000000..78456c6
--- /dev/null
+++ b/v17/leanback/res/values/attrs.xml
@@ -0,0 +1,164 @@
+<?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>
+    <declare-styleable name="lbBaseGridView">
+        <!-- Allow DPAD key to navigate out at the front of the View (where position = 0),
+             default is false  -->
+        <attr name="focusOutFront" format="boolean" />
+        <!-- Allow DPAD key to navigate out at the end of the view, default is false -->
+        <attr name="focusOutEnd" format="boolean" />
+        <!-- Defining margin between two items horizontally -->
+        <attr name="horizontalMargin" format="dimension" />
+        <!-- Defining margin between two items vertically -->
+        <attr name="verticalMargin" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="lbHorizontalGridView">
+        <!-- Defining height of each row of HorizontalGridView -->
+        <attr name="rowHeight" format="dimension" />
+        <!-- Defining number of rows -->
+        <attr name="numberOfRows" format="integer" />
+    </declare-styleable>
+
+    <declare-styleable name="lbVerticalGridView">
+        <!-- Defining width of each column of VerticalGridView -->
+        <attr name="columnWidth" format="dimension" />
+        <!-- Defining number of columns -->
+        <attr name="numberOfColumns" format="integer" />
+    </declare-styleable>
+
+    <declare-styleable name="lbBaseCardView">
+        <!-- Defines the type of the card layout -->
+        <attr name="cardType" format="enum">
+            <!-- A simple card layout with a single layout region. -->
+            <enum name="mainOnly" value="0" />
+            <!-- A card layout with two layout regions: a main area which is
+                 always visible, and an info region that appears over the lower
+                 area of the main region. -->
+            <enum name="infoOver" value="1" />
+            <!-- A card layout with two layout regions: a main area which is
+                 always visible, and an info region that appears below the main
+                 region. -->
+            <enum name="infoUnder" value="2" />
+            <!-- A card layout with three layout regions: a main area which is
+                 always visible, an info region that appears below the main
+                 region, and an extra region that appears below the info region
+                 after a small delay. -->
+            <enum name="infoUnderWithExtra" value="3" />
+        </attr>
+        <!-- Defines when the info region of a card layout is displayed. -->
+        <attr name="infoVisibility" format="enum">
+            <!-- Always display the info region. -->
+            <enum name="always" value="0"/>
+            <!-- Display the info region only when activated. -->
+            <enum name="activated" value="1"/>
+            <!-- Display the info region only when selected. -->
+            <enum name="selected" value="2"/>
+        </attr>
+        <!-- Defines when the extra region of a card layout is displayed.
+             Depends on infoVisibility, meaning the extra region never displays
+             if the info region is not displayed as well. -->
+        <attr name="extraVisibility" format="enum">
+            <!-- Always display the extra region. -->
+            <enum name="always" value="0"/>
+            <!-- Display the extra region only when activated. -->
+            <enum name="activated" value="1"/>
+            <!-- Display the extra region only when selected. -->
+            <enum name="selected" value="2"/>
+        </attr>
+        <!-- Defines the delay in milliseconds before the selection animation
+             runs for a card layout. -->
+        <attr name="selectedAnimationDelay" format="integer" />
+        <!-- Defines the duration in milliseconds of the selection animation for
+             a card layout. -->
+        <attr name="selectedAnimationDuration" format="integer" />
+        <!-- Defines the duration in milliseconds of the activated animation for
+             a card layout. -->
+        <attr name="activatedAnimationDuration" format="integer" />
+    </declare-styleable>
+
+    <!-- This is the basic set of layout attributes for elements within a card
+         layout. These attributes are specified with the rest of an elements's
+         normal attributes. -->
+    <declare-styleable name="lbBaseCardView_Layout">
+        <!-- The card layout region defined by this element. At most one of
+             element of each type should be specified as an immediate child of
+             the card layout. -->
+        <attr name="layout_viewType" format="enum">
+            <!-- The main region of the card. -->
+            <enum name="main" value="0"/>
+            <!-- The info region of the card. -->
+            <enum name="info" value="1"/>
+            <!-- The extra region of the card. -->
+            <enum name="extra" value="2"/>
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="LeanbackTheme">
+
+        <!-- left padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingLeft" format="dimension" />
+        <!-- right padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingRight" format="dimension" />
+        <!-- top padding of BrowseFragment -->
+        <attr name="browsePaddingTop" format="dimension" />
+        <!-- bottom padding of BrowseFragment -->
+        <attr name="browsePaddingBottom" format="dimension" />
+        <!-- start margin of RowsFragment inside BrowseFragment when HeadersFragment is visible -->
+        <attr name="browseRowsMarginStart" format="dimension" />
+        <!-- top margin of RowsFragment inside BrowseFragment when BrowseFragment title is visible -->
+        <attr name="browseRowsMarginTop" format="dimension" />
+        <!-- fading edge length of start of browse row when HeadersFragment is visible -->
+        <attr name="browseRowsFadingEdgeLength" format="dimension" />
+
+        <!-- BrowseFragment Title text style -->
+        <attr name="browseTitleTextStyle" format="reference" />
+
+        <!-- BrowseFragment Title icon style -->
+        <attr name="browseTitleIconStyle" format="reference" />
+
+        <!-- vertical grid style inside HeadersFragment -->
+        <attr name="headersVerticalGridStyle" format="reference" />
+
+        <!-- vertical grid style inside RowsFragment -->
+        <attr name="rowsVerticalGridStyle" format="reference" />
+
+        <!-- horizontal grid style inside a row -->
+        <attr name="rowHorizontalGridStyle" format="reference" />
+        <!-- header style inside a row -->
+        <attr name="rowHeaderStyle" format="reference" />
+
+        <!-- hover card title style -->
+        <attr name="rowHoverCardTitleStyle" format="reference" />
+        <!-- hover card description style -->
+        <attr name="rowHoverCardDescriptionStyle" format="reference" />
+
+        <!-- CardView styles -->
+        <attr name="baseCardViewStyle" format="reference" />
+        <attr name="imageCardViewStyle" format="reference" />
+
+        <!-- for details overviews -->
+        <attr name="detailsDescriptionTitleStyle" format="reference" />
+        <attr name="detailsDescriptionSubtitleStyle" format="reference" />
+        <attr name="detailsDescriptionBodyStyle" format="reference" />
+
+        <!-- style for a vertical grid of items -->
+        <attr name="itemsVerticalGridStyle" format="reference" />
+
+    </declare-styleable>
+</resources>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
new file mode 100644
index 0000000..0eeb825
--- /dev/null
+++ b/v17/leanback/res/values/colors.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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="lb_grey">#888888</color>
+
+    <color name="lb_browse_title_color">#EEEEEE</color>
+    <color name="lb_browse_header_color">#EEEEEE</color>
+
+    <color name="lb_list_item_unselected_text_color">#FFF1F1F1</color>
+    <color name="lb_background_protection">#A0333333</color>
+
+    <color name="lb_view_dim_mask_color">#000000</color>
+    <item name="lb_view_dimmed_level" type="dimen">60%</item>
+
+    <color name="lb_details_description_color">#EEEEEE</color>
+
+    <color name="lb_action_text_color">#EEEEEE</color>
+    <color name="lb_action_bg_color">#3D3D3D</color>
+
+    <color name="lb_search_bar_text_color">#FFEEEEEE</color>
+    <color name="lb_search_bar_hint_color">#33EEEEEE</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_title_text_color">#FFEEEEEE</color>
+    <color name="lb_basic_card_content_text_color">#FFEEEEEE</color>
+</resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
new file mode 100644
index 0000000..61792fa
--- /dev/null
+++ b/v17/leanback/res/values/dimens.xml
@@ -0,0 +1,117 @@
+<?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>
+    <dimen name="lb_browse_padding_left">56dp</dimen>
+    <dimen name="lb_browse_padding_top">27dp</dimen>
+    <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">120dp</dimen>
+    <dimen name="lb_browse_rows_fading_edge">16dp</dimen>
+
+    <dimen name="lb_browse_title_height">60dp</dimen>
+    <dimen name="lb_browse_title_icon_height">52dp</dimen>
+    <dimen name="lb_browse_title_icon_width">52dp</dimen>
+    <dimen name="lb_browse_title_icon_margin_right">24dp</dimen>
+    <dimen name="lb_browse_title_text_size">28sp</dimen>
+
+    <integer name="lb_browse_headers_transition_delay">250</integer>
+
+    <integer name="lb_browse_rows_anim_duration">250</integer>
+
+    <dimen name="lb_browse_headers_vertical_margin">12dp</dimen>
+    <dimen name="lb_browse_header_height">48dp</dimen>
+    <dimen name="lb_browse_header_half_height">24dp</dimen>
+    <dimen name="lb_browse_header_text_size">22sp</dimen>
+    <item name="lb_browse_header_select_duration" format="integer" type="dimen">150</item>
+    <item name="lb_browse_header_unselect_alpha" format="float" type="dimen">0.5</item>
+    <item name="lb_browse_header_select_scale" format="float" type="dimen">1.1</item>
+
+    <dimen name="lb_browse_row_list_height">224dp</dimen>
+    <dimen name="lb_browse_row_title_height">24dp</dimen>
+    <dimen name="lb_browse_row_hovercard_max_width">420dp</dimen>
+    <dimen name="lb_browse_row_hovercard_title_font_size">18sp</dimen>
+    <dimen name="lb_browse_row_hovercard_description_font_size">12sp</dimen>
+    <dimen name="lb_browse_row_header_text_size">18sp</dimen>
+    <dimen name="lb_browse_item_margin">12dp</dimen>
+    <dimen name="lb_browse_item_margin_vertical">12dp</dimen>
+    <dimen name="lb_browse_item_margin_horizontal">12dp</dimen>
+
+    <item name="lb_focus_zoom_factor_small" type="fraction">106%</item>
+    <item name="lb_focus_zoom_factor_medium" type="fraction">110%</item>
+    <item name="lb_focus_zoom_factor_large" type="fraction">114%</item>
+
+    <dimen name="lb_details_overview_image_width">132dp</dimen>
+    <dimen name="lb_details_overview_image_margin_left">132dp</dimen>
+    <dimen name="lb_details_overview_description_intertext_spacing">16dp</dimen>
+    <dimen name="lb_details_overview_description_margin_left">30dp</dimen>
+    <dimen name="lb_details_overview_description_margin_right">132dp</dimen>
+    <dimen name="lb_details_overiew_actions_margin_top">19dp</dimen>
+    <dimen name="lb_details_overview_action_items_margin">32dp</dimen>
+    <item name="lb_details_overview_action_select_duration" format="integer" type="dimen">150</item>
+    <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_rows_align_top">120dp</dimen>
+
+    <dimen name="lb_details_description_title_text_size">34sp</dimen>
+    <dimen name="lb_details_description_title_leading_space">42sp</dimen>
+    <dimen name="lb_details_description_subtitle_text_size">14sp</dimen>
+    <dimen name="lb_details_description_body_text_size">14sp</dimen>
+    <integer name="lb_details_description_title_max_lines">2</integer>
+    <integer name="lb_details_description_subtitle_max_lines">1</integer>
+    <integer name="lb_details_description_body_max_lines">5</integer>
+
+    <dimen name="lb_action_1_line_height">36dp</dimen>
+    <dimen name="lb_action_1_line_padding_left">32dp</dimen>
+    <dimen name="lb_action_2_lines_height">54dp</dimen>
+    <dimen name="lb_action_padding_right">32dp</dimen>
+    <dimen name="lb_action_icon_margin">12dp</dimen>
+    <dimen name="lb_action_icon_width">30dp</dimen>
+    <dimen name="lb_action_text_size">16sp</dimen>
+    <dimen name="lb_action_text_spacing">2sp</dimen>
+
+    <!-- Search bar -->
+    <dimen name="lb_search_bar_height">60dp</dimen>
+    <dimen name="lb_search_bar_padding_left">56dp</dimen>
+    <dimen name="lb_search_bar_padding_top">27dp</dimen>
+
+    <dimen name="lb_search_bar_text_size">28sp</dimen>
+    <dimen name="lb_search_bar_items_layout_margin_top">27dp</dimen>
+    <dimen name="lb_search_bar_items_width">660dp</dimen>
+
+    <!-- Search Fragment -->
+    <dimen name="lb_search_browse_rows_align_top">120dp</dimen>
+    <dimen name="lb_search_browse_row_padding_left">56dp</dimen>
+
+    <dimen name="lb_search_orb_size">52dp</dimen>
+
+    <dimen name="lb_search_orb_margin_top">4dp</dimen>
+    <dimen name="lb_search_orb_margin_bottom">4dp</dimen>
+    <dimen name="lb_search_orb_margin_left">4dp</dimen>
+    <dimen name="lb_search_orb_margin_right">4dp</dimen>
+
+    <!-- BasicCardView -->
+    <dimen name="lb_basic_card_main_width">140dp</dimen>
+    <dimen name="lb_basic_card_main_height">188dp</dimen>
+    <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_info_badge_size">16dp</dimen>
+</resources>
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
new file mode 100644
index 0000000..4010b2c
--- /dev/null
+++ b/v17/leanback/res/values/ids.xml
@@ -0,0 +1,19 @@
+<?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>
+     <item type="id" name="lb_focus_animator" />
+ </resources>
\ No newline at end of file
diff --git a/v17/leanback/res/values/integers.xml b/v17/leanback/res/values/integers.xml
new file mode 100644
index 0000000..4fbe83f
--- /dev/null
+++ b/v17/leanback/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?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>
+    <integer name="lb_card_selected_animation_delay">400</integer>
+    <integer name="lb_card_selected_animation_duration">150</integer>
+    <integer name="lb_card_activated_animation_duration">150</integer>
+</resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
new file mode 100644
index 0000000..333c06a
--- /dev/null
+++ b/v17/leanback/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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>
+    <string name="orb_search_label">Search</string>
+    <string name="orb_search_action">Search Action</string>
+    <string name="lb_search_bar_hint">Search</string>
+</resources>
\ No newline at end of file
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
new file mode 100644
index 0000000..955e9fe
--- /dev/null
+++ b/v17/leanback/res/values/styles.xml
@@ -0,0 +1,196 @@
+<?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">
+    <style name="TextAppearance.Leanback" parent="android:TextAppearance.Holo">
+        <!-- Any text appearance overrides go here. -->
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Title" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_title_text_size</item>
+        <item name="android:textColor">@color/lb_browse_title_color</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.Header" parent="TextAppearance.Leanback">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">@dimen/lb_browse_row_header_text_size</item>
+        <item name="android:textColor">@color/lb_browse_header_color</item>
+        </style>
+
+    <style name="TextAppearance.Leanback.Header" parent="TextAppearance.Leanback">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">@dimen/lb_browse_header_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.SearchLabel" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_header_text_size</item>
+        <item name="android:textColor">@color/lb_list_item_unselected_text_color</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.SearchTextEdit" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_search_bar_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionTitle">
+        <item name="android:textSize">@dimen/lb_details_description_title_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_color</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionSubtitle">
+        <item name="android:textSize">@dimen/lb_details_description_subtitle_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_color</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionBody">
+        <item name="android:textSize">@dimen/lb_details_description_body_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_color</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="Widget.Leanback" parent="android:Widget.Holo" />
+
+    <style name="Widget.Leanback.BaseCardViewStyle" />
+
+    <style name="Widget.Leanback.ImageCardViewStyle" parent="Widget.Leanback.BaseCardViewStyle">
+        <item name="cardType">infoUnder</item>
+        <item name="infoVisibility">activated</item>
+        <item name="android:background">@color/lb_basic_card_bg_color</item>
+    </style>
+
+    <style name="Widget.Leanback.Title" />
+
+    <style name="Widget.Leanback.Title.Text">
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Title</item>
+    </style>
+
+    <style name="Widget.Leanback.Title.Icon">
+        <item name="android:scaleType">centerInside</item>
+    </style>
+
+    <!-- HeadersFragment (fast lane) -->
+    <style name="Widget.Leanback.Headers" />
+
+    <!-- RowsFragment -->
+    <style name="Widget.Leanback.Rows" >
+    </style>
+
+    <!-- row view -->
+    <style name="Widget.Leanback.Row" >
+    </style>
+
+    <style name="Widget.Leanback.GridItems" />
+
+    <style name="Widget.Leanback.Headers.VerticalGridView" >
+        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+        <item name="android:clipToPadding">false</item>
+        <item name="focusOutFront">true</item>
+        <item name="focusOutEnd">true</item>
+        <item name="verticalMargin">@dimen/lb_browse_headers_vertical_margin</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Header" >
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Header</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Rows.VerticalGridView" >
+        <item name="android:paddingBottom">?attr/browsePaddingBottom</item>
+        <item name="android:clipToPadding">false</item>
+        <item name="focusOutFront">true</item>
+        <item name="focusOutEnd">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HorizontalGridView">
+        <item name="android:clipToPadding">false</item>
+        <item name="android:focusable">true</item>
+        <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_vertical</item>
+        <item name="android:paddingTop">@dimen/lb_browse_item_margin_vertical</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="focusOutFront">true</item>
+    </style>
+
+    <style name="Widget.Leanback.GridItems.VerticalGridView">
+        <item name="android:clipToPadding">false</item>
+        <item name="android:focusable">true</item>
+        <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_vertical</item>
+        <item name="android:paddingTop">@dimen/lb_browse_item_margin_vertical</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_margin</item>
+        <item name="focusOutFront">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.Header">
+        <item name="android:minHeight">@dimen/lb_browse_row_title_height</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.Header</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.HoverCardTitle" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_row_hovercard_title_font_size</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.HoverCardDescription" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_row_hovercard_description_font_size</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HoverCardTitle" >
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardTitle</item>
+        <item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HoverCardDescription" >
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardDescription</item>
+        <item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:maxLines">2</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionTitleStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionTitle</item>
+        <item name="android:maxLines">@integer/lb_details_description_title_max_lines</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionSubtitleStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionSubtitle</item>
+        <item name="android:maxLines">@integer/lb_details_description_subtitle_max_lines</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionBodyStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionBody</item>
+        <item name="android:maxLines">@integer/lb_details_description_body_max_lines</item>
+    </style>
+</resources>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
new file mode 100644
index 0000000..b683b28
--- /dev/null
+++ b/v17/leanback/res/values/themes.xml
@@ -0,0 +1,51 @@
+<?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>
+
+    <style name="Theme.Leanback" parent="android:Theme.Holo.NoActionBar">
+
+        <item name="android:windowOverscan">true</item>
+
+        <item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
+        <item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
+
+        <item name="browsePaddingLeft">@dimen/lb_browse_padding_left</item>
+        <item name="browsePaddingRight">@dimen/lb_browse_padding_right</item>
+        <item name="browsePaddingTop">@dimen/lb_browse_padding_top</item>
+        <item name="browsePaddingBottom">@dimen/lb_browse_padding_bottom</item>
+        <item name="browseRowsMarginStart">@dimen/lb_browse_rows_margin_start</item>
+        <item name="browseRowsMarginTop">@dimen/lb_browse_rows_margin_top</item>
+        <item name="browseRowsFadingEdgeLength">@dimen/lb_browse_rows_fading_edge</item>
+
+        <item name="headersVerticalGridStyle">@style/Widget.Leanback.Headers.VerticalGridView</item>
+        <item name="rowsVerticalGridStyle">@style/Widget.Leanback.Rows.VerticalGridView</item>
+        <item name="rowHorizontalGridStyle">@style/Widget.Leanback.Row.HorizontalGridView</item>
+        <item name="itemsVerticalGridStyle">@style/Widget.Leanback.GridItems.VerticalGridView</item>
+
+        <item name="browseTitleTextStyle">@style/Widget.Leanback.Title.Text</item>
+        <item name="browseTitleIconStyle">@style/Widget.Leanback.Title.Icon</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="detailsDescriptionTitleStyle">@style/Widget.Leanback.DetailsDescriptionTitleStyle</item>
+        <item name="detailsDescriptionSubtitleStyle">@style/Widget.Leanback.DetailsDescriptionSubtitleStyle</item>
+        <item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
+    </style>
+
+</resources>
diff --git a/v17/leanback/src/.readme b/v17/leanback/src/.readme
new file mode 100644
index 0000000..4bcebad
--- /dev/null
+++ b/v17/leanback/src/.readme
@@ -0,0 +1,2 @@
+This hidden file is there to ensure there is an src folder.
+Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
new file mode 100644
index 0000000..334dd9d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+
+/**
+ * Fragment used by the background manager.
+ * @hide
+ */
+public final class BackgroundFragment extends Fragment {
+    private BackgroundManager mBackgroundManager;
+
+    void setBackgroundManager(BackgroundManager backgroundManager) {
+        mBackgroundManager = backgroundManager;
+    }
+
+    BackgroundManager getBackgroundManager() {
+        return mBackgroundManager;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // mBackgroundManager might be null:
+        // if BackgroundFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.onActivityResume();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // mBackgroundManager might be null:
+        // if BackgroundFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.detach();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
new file mode 100644
index 0000000..8b56775
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -0,0 +1,724 @@
+/*
+ * 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.R;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+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.LinearInterpolator;
+
+/**
+ * Supports background image continuity between multiple Activities.
+ *
+ * <p>An Activity should instantiate a BackgroundManager and {@link #attach}
+ * to the Activity's window.  When the Activity is started, the background is
+ * initialized to the current background values stored in a continuity service.
+ * The background continuity service is updated as the background is updated.
+ *
+ * <p>At some point, for example when it is stopped, the Activity may release
+ * its background state.
+ *
+ * <p>When an Activity is resumed, if the BackgroundManager has not been
+ * released, the continuity service is updated from the BackgroundManager state.
+ * If the BackgroundManager was released, the BackgroundManager inherits the
+ * current state from the continuity service.
+ *
+ * <p>When the last Activity is destroyed, the background state is reset.
+ *
+ * <p>Backgrounds consist of several layers, from back to front:
+ * <ul>
+ *   <li>the background Drawable of the theme</li>
+ *   <li>a solid color (set via {@link #setColor})</li>
+ *   <li>two Drawables, previous and current (set via {@link #setBitmap} or
+ *   {@link #setDrawable}), which may be in transition</li>
+ * </ul>
+ *
+ * <p>BackgroundManager holds references to potentially large bitmap Drawables.
+ * Call {@link #release} to release these references when the Activity is not
+ * visible.
+ */
+// TODO: support for multiple app processes requires a proper android service
+// instead of the shared memory "service" implemented here. Such a service could
+// support continuity between fragments of different applications if desired.
+public final class BackgroundManager {
+    private static final String TAG = "BackgroundManager";
+    private static final boolean DEBUG = false;
+
+    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;
+
+    /**
+     * Using a separate window for backgrounds can improve graphics performance by
+     * leveraging hardware display layers.
+     * TODO: support a leanback configuration option.
+     */
+    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();
+
+    private Context mContext;
+    private Handler mHandler;
+    private Window mWindow;
+    private WindowManager mWindowManager;
+    private View mBgView;
+    private BackgroundContinuityService mService;
+    private int mThemeDrawableResourceId;
+
+    private int mHeightPx;
+    private int mWidthPx;
+    private Drawable mBackgroundDrawable;
+    private int mBackgroundColor;
+    private boolean mAttached;
+
+    private class DrawableWrapper {
+        protected int mAlpha;
+        protected Drawable mDrawable;
+        protected ObjectAnimator mAnimator;
+        protected boolean mAnimationPending;
+
+        public DrawableWrapper(Drawable drawable) {
+            mDrawable = drawable;
+            setAlpha(FULL_ALPHA);
+        }
+
+        public Drawable getDrawable() {
+            return mDrawable;
+        }
+        public void setAlpha(int alpha) {
+            mAlpha = alpha;
+            mDrawable.setAlpha(alpha);
+        }
+        public int getAlpha() {
+            return mAlpha;
+        }
+        public void setColor(int color) {
+            ((ColorDrawable) mDrawable).setColor(color);
+        }
+        public void fadeIn(int durationMs, int delayMs) {
+            fade(durationMs, delayMs, FULL_ALPHA);
+        }
+        public void fadeOut(int durationMs) {
+            fade(durationMs, 0, 0);
+        }
+        public void fade(int durationMs, int delayMs, int alpha) {
+            if (mAnimator != null && mAnimator.isStarted()) {
+                mAnimator.cancel();
+            }
+            mAnimator = ObjectAnimator.ofInt(this, "alpha", alpha);
+            mAnimator.setInterpolator(new LinearInterpolator());
+            mAnimator.setDuration(durationMs);
+            mAnimator.setStartDelay(delayMs);
+            mAnimationPending = true;
+        }
+        public boolean isAnimationPending() {
+            return mAnimationPending;
+        }
+        public boolean isAnimationStarted() {
+            return mAnimator != null && mAnimator.isStarted();
+        }
+        public void startAnimation() {
+            mAnimator.start();
+            mAnimationPending = false;
+        }
+    }
+
+    private LayerDrawable mLayerDrawable;
+    private DrawableWrapper mLayerWrapper;
+    private DrawableWrapper mImageInWrapper;
+    private DrawableWrapper mImageOutWrapper;
+    private DrawableWrapper mColorWrapper;
+    private DrawableWrapper mDimWrapper;
+
+    private Drawable mThemeDrawable;
+    private ChangeBackgroundRunnable mChangeRunnable;
+
+    /**
+     * Shared memory continuity service.
+     */
+    private static class BackgroundContinuityService {
+        private static final String TAG = "BackgroundContinuityService";
+        private static boolean DEBUG = BackgroundManager.DEBUG;
+
+        private static BackgroundContinuityService sService = new BackgroundContinuityService();
+
+        private int mColor;
+        private Drawable mDrawable;
+        private int mCount;
+
+        private BackgroundContinuityService() {
+            reset();
+        }
+
+        private void reset() {
+            mColor = Color.TRANSPARENT;
+            mDrawable = null;
+        }
+
+        public static BackgroundContinuityService getInstance() {
+            final int count = sService.mCount++;
+            if (DEBUG) Log.v(TAG, "Returning instance with new count " + count);
+            return sService;
+        }
+
+        public void unref() {
+            if (mCount <= 0) throw new IllegalStateException("Can't unref, count " + mCount);
+            if (--mCount == 0) {
+                if (DEBUG) Log.v(TAG, "mCount is zero, resetting");
+                reset();
+            }
+        }
+        public int getColor() {
+            return mColor;
+        }
+        public Drawable getDrawable() {
+            return mDrawable;
+        }
+        public void setColor(int color) {
+            mColor = color;
+        }
+        public void setDrawable(Drawable drawable) {
+            mDrawable = drawable;
+        }
+    }
+
+    private Drawable getThemeDrawable() {
+        Drawable drawable = null;
+        if (mThemeDrawableResourceId != -1) {
+            drawable = mContext.getResources().getDrawable(mThemeDrawableResourceId);
+        }
+        if (drawable == null) {
+            drawable = createEmptyDrawable();
+        }
+        return drawable;
+    }
+
+    /**
+     * Get the BackgroundManager associated with the Activity.
+     * <p>
+     * The BackgroundManager will be created on-demand for each individual
+     * Activity. Subsequent calls will return the same BackgroundManager created
+     * for this Activity.
+     */
+    public static BackgroundManager getInstance(Activity activity) {
+        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
+                .findFragmentByTag(FRAGMENT_TAG);
+        if (fragment != null) {
+            BackgroundManager manager = fragment.getBackgroundManager();
+            if (manager != null) {
+                return manager;
+            }
+            // manager is null: this is a fragment restored by FragmentManager,
+            // fall through to create a BackgroundManager attach to it.
+        }
+        return new BackgroundManager(activity);
+    }
+
+    /**
+     * Construct a BackgroundManager instance. The Initial background is set
+     * from the continuity service.
+     * @deprecated Use getInstance(Activity).
+     */
+    @Deprecated
+    public BackgroundManager(Activity activity) {
+        mContext = activity;
+        mService = BackgroundContinuityService.getInstance();
+        mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
+        mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
+        mHandler = new Handler();
+
+        TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
+                android.R.attr.windowBackground });
+        mThemeDrawableResourceId = ta.getResourceId(0, -1);
+        if (mThemeDrawableResourceId < 0) {
+            if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!");
+        }
+        ta.recycle();
+
+        createFragment(activity);
+    }
+
+    private void createFragment(Activity activity) {
+        // Use a fragment to ensure the background manager gets detached properly.
+        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
+                .findFragmentByTag(FRAGMENT_TAG);
+        if (fragment == null) {
+            fragment = new BackgroundFragment();
+            activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit();
+        } else {
+            if (fragment.getBackgroundManager() != null) {
+                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
+                        "activity, please use getInstance() instead");
+            }
+        }
+        fragment.setBackgroundManager(this);
+    }
+
+    /**
+     * Synchronizes state when the owning Activity is resumed.
+     */
+    void onActivityResume() {
+        if (mService == null) {
+            return;
+        }
+        if (mLayerDrawable == null) {
+            if (DEBUG) Log.v(TAG, "onActivityResume: released state, syncing with service");
+            syncWithService();
+        } else {
+            if (DEBUG) Log.v(TAG, "onActivityResume: updating service color "
+                    + mBackgroundColor + " drawable " + mBackgroundDrawable);
+            mService.setColor(mBackgroundColor);
+            mService.setDrawable(mBackgroundDrawable);
+        }
+    }
+
+    private void syncWithService() {
+        int color = mService.getColor();
+        Drawable drawable = mService.getDrawable();
+
+        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;
+
+        updateImmediate();
+    }
+
+    private void lazyInit() {
+        if (mLayerDrawable != null) {
+            return;
+        }
+
+        mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable(
+                R.drawable.lb_background);
+        mBgView.setBackground(mLayerDrawable);
+
+        mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
+
+        mDimWrapper = new DrawableWrapper(
+                mLayerDrawable.findDrawableByLayerId(R.id.background_dim));
+
+        mLayerWrapper = new DrawableWrapper(mLayerDrawable);
+
+        mColorWrapper = new DrawableWrapper(
+                mLayerDrawable.findDrawableByLayerId(R.id.background_color));
+    }
+
+    /**
+     * Make the background visible on the given Window.
+     */
+    public void attach(Window window) {
+        if (USE_SEPARATE_WINDOW) {
+            attachBehindWindow(window);
+        } else {
+            attachToView(window.getDecorView());
+        }
+    }
+
+    private void attachBehindWindow(Window window) {
+        if (DEBUG) Log.v(TAG, "attachBehindWindow " + window);
+        mWindow = window;
+        mWindowManager = window.getWindowManager();
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                // Media window sits behind the main application window
+                WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
+                // Avoid default to software format RGBA
+                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                android.graphics.PixelFormat.TRANSLUCENT);
+        params.setTitle(WINDOW_NAME);
+        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+
+        View backgroundView = LayoutInflater.from(mContext).inflate(
+                R.layout.lb_background_window, null);
+        mWindowManager.addView(backgroundView, params);
+
+        attachToView(backgroundView);
+    }
+
+    private void attachToView(View sceneRoot) {
+        mBgView = sceneRoot;
+        mAttached = true;
+        syncWithService();
+    }
+
+    /**
+     * Release references to Drawables and put the BackgroundManager into the
+     * detached state. Called when the associated Activity is destroyed.
+     * @hide
+     */
+    void detach() {
+        if (DEBUG) Log.v(TAG, "detach");
+        release();
+
+        if (mWindowManager != null && mBgView != null) {
+            mWindowManager.removeViewImmediate(mBgView);
+        }
+
+        mWindowManager = null;
+        mWindow = null;
+        mBgView = null;
+        mAttached = false;
+
+        if (mService != null) {
+            mService.unref();
+            mService = null;
+        }
+    }
+
+    /**
+     * Release references to Drawables. Typically called to reduce memory
+     * overhead when not visible.
+     * <p>
+     * When an Activity is resumed, if the BackgroundManager has not been
+     * released, the continuity service is updated from the BackgroundManager
+     * state. If the BackgroundManager was released, the BackgroundManager
+     * inherits the current state from the continuity service.
+     */
+    public void release() {
+        if (DEBUG) Log.v(TAG, "release");
+        if (mLayerDrawable != null) {
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
+            mLayerDrawable = null;
+        }
+        mLayerWrapper = null;
+        mImageInWrapper = null;
+        mImageOutWrapper = null;
+        mColorWrapper = null;
+        mDimWrapper = null;
+        mThemeDrawable = null;
+        if (mChangeRunnable != null) {
+            mChangeRunnable.cancel();
+            mChangeRunnable = null;
+        }
+        releaseBackgroundBitmap();
+    }
+
+    private void releaseBackgroundBitmap() {
+        mBackgroundDrawable = null;
+    }
+
+    private void updateImmediate() {
+        lazyInit();
+
+        mColorWrapper.setColor(mBackgroundColor);
+        if (mDimWrapper != null) {
+            mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID);
+        }
+        showWallpaper(mBackgroundColor == Color.TRANSPARENT);
+
+        mThemeDrawable = getThemeDrawable();
+        mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable);
+
+        if (mBackgroundDrawable == null) {
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+        } else {
+            if (DEBUG) Log.v(TAG, "Background drawable is available");
+            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
+            if (mDimWrapper != null) {
+                mDimWrapper.setAlpha(FULL_ALPHA);
+            }
+        }
+    }
+
+    /**
+     * Set the background to the given color. The timing for when this becomes
+     * visible in the app is undefined and may take place after a small delay.
+     */
+    public void setColor(int color) {
+        if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));
+
+        mBackgroundColor = color;
+        mService.setColor(mBackgroundColor);
+
+        if (mColorWrapper != null) {
+            mColorWrapper.setColor(mBackgroundColor);
+        }
+    }
+
+    /**
+     * Set the given drawable into the background. The provided Drawable will be
+     * used unmodified as the background, without any scaling or cropping
+     * applied to it. The timing for when this becomes visible in the app is
+     * undefined and may take place after a small delay.
+     */
+    public void setDrawable(Drawable drawable) {
+        if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
+        setDrawableInternal(drawable);
+    }
+
+    private void setDrawableInternal(Drawable drawable) {
+        if (!mAttached) {
+            throw new IllegalStateException("Must attach before setting background drawable");
+        }
+
+        if (mChangeRunnable != null) {
+            mChangeRunnable.cancel();
+        }
+        mChangeRunnable = new ChangeBackgroundRunnable(drawable);
+
+        mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
+    }
+
+    /**
+     * Set the given bitmap into the background. When using setBitmap to set the
+     * background, the provided bitmap will be scaled and cropped to correctly
+     * fit within the dimensions of the view. The timing for when this becomes
+     * visible in the app is undefined and may take place after a small delay.
+     */
+    public void setBitmap(Bitmap bitmap) {
+        if (DEBUG) {
+            Log.v(TAG, "setBitmap " + bitmap);
+        }
+
+        if (bitmap == null) {
+            setDrawableInternal(null);
+            return;
+        }
+
+        if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+            if (DEBUG) {
+                Log.v(TAG, "invalid bitmap width or height");
+            }
+            return;
+        }
+
+        if (mBackgroundDrawable instanceof BitmapDrawable &&
+                ((BitmapDrawable) mBackgroundDrawable).getBitmap() == bitmap) {
+            if (DEBUG) {
+                Log.v(TAG, "same bitmap detected");
+            }
+            mService.setDrawable(mBackgroundDrawable);
+            return;
+        }
+
+        if (SCALE_BITMAPS_TO_FIT &&
+                (bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
+            // Scale proportionately to fit width and height.
+
+            Matrix matrix = new Matrix();
+
+            int dwidth = bitmap.getWidth();
+            int dheight = bitmap.getHeight();
+            float scale;
+            int dx;
+
+            if (DEBUG) {
+                Log.v(TAG, "original image size " + dwidth + "x" + dheight);
+            }
+
+            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);
+
+            bitmap = Bitmap.createBitmap(bitmap, dx, 0, subX, subY, matrix, true);
+            if (DEBUG) {
+                Log.v(TAG, "new image size " + bitmap.getWidth() + "x" + bitmap.getHeight());
+            }
+        }
+
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+
+        setDrawableInternal(bitmapDrawable);
+    }
+
+    private void applyBackgroundChanges() {
+        if (!mAttached || mLayerWrapper == null) {
+            return;
+        }
+
+        if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);
+
+        int dimAlpha = 0;
+
+        if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) {
+            if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting");
+            mImageOutWrapper.startAnimation();
+            mImageOutWrapper = null;
+            dimAlpha = DIM_ALPHA_ON_SOLID;
+        }
+
+        if (mImageInWrapper == null && mBackgroundDrawable != null) {
+            if (DEBUG) Log.v(TAG, "creating new imagein drawable");
+            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
+            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();
+            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.startAnimation();
+        }
+    }
+
+    /**
+     * Returns the current background color.
+     */
+    public final int getColor() {
+        return mBackgroundColor;
+    }
+
+    /**
+     * Returns the current background {@link Drawable}.
+     */
+    public Drawable getDrawable() {
+        return mBackgroundDrawable;
+    }
+
+    /**
+     * Task which changes the background.
+     */
+    class ChangeBackgroundRunnable implements Runnable {
+        private Drawable mDrawable;
+        private boolean mCancel;
+
+        ChangeBackgroundRunnable(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        public void cancel() {
+            mCancel = true;
+        }
+
+        @Override
+        public void run() {
+            if (!mCancel) {
+                runTask();
+            }
+        }
+
+        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) {
+                return;
+            }
+
+            releaseBackgroundBitmap();
+
+            if (mImageInWrapper != null) {
+                mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
+                mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha());
+                mImageOutWrapper.fadeOut(FADE_DURATION_QUICK);
+
+                // Order is important! Setting a drawable "removes" the
+                // previous one from the view
+                mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+                mLayerDrawable.setDrawableByLayerId(R.id.background_imageout,
+                        mImageOutWrapper.getDrawable());
+                mImageInWrapper.setAlpha(0);
+                mImageInWrapper = null;
+            }
+
+            mBackgroundDrawable = mDrawable;
+            mService.setDrawable(mBackgroundDrawable);
+
+            applyBackgroundChanges();
+        }
+    }
+
+    private Drawable createEmptyDrawable() {
+        Bitmap bitmap = null;
+        return new BitmapDrawable(mContext.getResources(), bitmap);
+    }
+
+    private void showWallpaper(boolean show) {
+        if (mWindow == null) {
+            return;
+        }
+
+        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
+        if (show) {
+            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "showing wallpaper");
+            layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        } else {
+            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "hiding wallpaper");
+            layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        }
+
+        mWindow.setAttributes(layoutParams);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
new file mode 100644
index 0000000..2be3e54
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ */
+abstract class BaseRowFragment extends Fragment {
+    private ObjectAdapter mAdapter;
+    private VerticalGridView mVerticalGridView;
+    private PresenterSelector mPresenterSelector;
+    private ItemBridgeAdapter mBridgeAdapter;
+    private int mSelectedPosition = -1;
+
+    abstract protected int getLayoutResourceId();
+
+    private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
+        @Override
+        public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+            onRowSelected(parent, view, position, id);
+        }
+    };
+
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mVerticalGridView = (VerticalGridView) inflater.inflate(getLayoutResourceId(), container, false);
+        return mVerticalGridView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (mBridgeAdapter != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+        mVerticalGridView.setOnChildSelectedListener(mRowSelectedListener);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mVerticalGridView = null;
+    }
+
+    /**
+     * Set the presenter selector used to create and bind views.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        mPresenterSelector = presenterSelector;
+        updateAdapter();
+    }
+
+    /**
+     * Get the presenter selector used to create and bind views.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Sets the adapter for the fragment.
+     */
+    public final void setAdapter(ObjectAdapter rowsAdapter) {
+        mAdapter = rowsAdapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Returns the bridge adapter.
+     */
+    protected final ItemBridgeAdapter getBridgeAdapter() {
+        return mBridgeAdapter;
+    }
+
+    /**
+     * Set the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+            mVerticalGridView.setSelectedPositionSmooth(position);
+        }
+    }
+
+    final VerticalGridView getVerticalGridView() {
+        return mVerticalGridView;
+    }
+
+    protected void updateAdapter() {
+        mBridgeAdapter = null;
+
+        if (mAdapter != null) {
+            // If presenter selector is null, adapter ps will be used
+            mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
+        }
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mBridgeAdapter != null && mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+
+    protected Object getItem(Row row, int position) {
+        if (row instanceof ListRow) {
+            return ((ListRow) row).getAdapter().get(position);
+        } else {
+            return 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
new file mode 100644
index 0000000..0df07f4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -0,0 +1,660 @@
+/*
+ * 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.R;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.Presenter;
+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.util.Log;
+import android.util.SparseIntArray;
+import android.app.Fragment;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
+import android.widget.TextView;
+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.
+ *
+ */
+public class BrowseFragment extends Fragment {
+    private static final String TAG = "BrowseFragment";
+    private static boolean DEBUG = false;
+
+    /** The fastlane navigation panel is enabled and shown by default. */
+    public static final int HEADERS_ENABLED = 1;
+
+    /** The fastlane navigation panel is enabled and hidden by default. */
+    public static final int HEADERS_HIDDEN = 2;
+
+    /** The fastlane navigation panel is disabled and will never be shown. */
+    public static final int HEADERS_DISABLED = 3;
+
+    private RowsFragment mRowsFragment;
+    private HeadersFragment mHeadersFragment;
+
+    private ObjectAdapter mAdapter;
+
+    private Params mParams;
+    private BrowseFrameLayout mBrowseFrame;
+    private ImageView mBadgeView;
+    private TextView mTitleView;
+    private ViewGroup mBrowseTitle;
+    private SearchOrbView mSearchOrbView;
+    private boolean mShowingTitle = true;
+    private boolean mShowingHeaders = true;
+    private boolean mCanShowHeaders = true;
+    private int mContainerListMarginLeft;
+    private int mContainerListAlignTop;
+    private TransitionHelper mTransitionHelper;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnClickListener mExternalOnSearchClickedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private int mSelectedPosition = -1;
+
+    // transition related:
+    private static int sReparentHeaderId = View.generateViewId();
+    private Object mSceneWithTitle;
+    private Object mSceneWithoutTitle;
+    private Object mSceneWithHeaders;
+    private Object mSceneWithoutHeaders;
+    private Object mTitleTransition;
+    private Object mHeadersTransition;
+    private int mHeadersTransitionStartDelay;
+
+    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
+    private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge";
+    private static final String ARG_HEADERS_STATE =
+        BrowseFragment.class.getCanonicalName() + ".headersState";
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String title, String badgeUri) {
+        return createArgs(args, title, badgeUri, HEADERS_ENABLED);
+    }
+
+    public static Bundle createArgs(Bundle args, String title, String badgeUri, 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;
+    }
+
+    public static class Params {
+        private String mTitle;
+        private Drawable mBadgeDrawable;
+        private int mHeadersState;
+
+        /**
+         * Sets the badge image.
+         */
+        public void setBadgeImage(Drawable drawable) {
+            mBadgeDrawable = drawable;
+        }
+
+        /**
+         * Returns the badge image.
+         */
+        public Drawable getBadgeImage() {
+            return mBadgeDrawable;
+        }
+
+        /**
+         * Sets a title for the browse fragment.
+         */
+        public void setTitle(String title) {
+            mTitle = title;
+        }
+
+        /**
+         * Returns the title for the browse fragment.
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Sets the state for the headers column in the browse fragment.
+         */
+        public void setHeadersState(int headersState) {
+            if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+                Log.e(TAG, "Invalid headers state: " + headersState
+                        + ", default to enabled and shown.");
+                mHeadersState = HEADERS_ENABLED;
+            } else {
+                mHeadersState = headersState;
+            }
+        }
+
+        /**
+         * Returns the state for the headers column in the browse fragment.
+         */
+        public int getHeadersState() {
+            return mHeadersState;
+        }
+    }
+
+    /**
+     * Set browse parameters.
+     */
+    public void setBrowseParams(Params params) {
+        mParams = params;
+        setBadgeDrawable(mParams.mBadgeDrawable);
+        setTitle(mParams.mTitle);
+        setHeadersState(mParams.mHeadersState);
+    }
+
+    /**
+     * Returns browse parameters.
+     */
+    public Params getBrowseParams() {
+        return mParams;
+    }
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+            mHeadersFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * 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.
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * 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.
+     *
+     * The listener onClick method will be invoked when the user click on the search action.
+     *
+     * @param listener The listener.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mSearchOrbView != null) {
+            mSearchOrbView.setOnOrbClickedListener(listener);
+        }
+    }
+
+    private void onHeadersTransitionStart(boolean withHeaders) {
+        mRowsFragment.getVerticalGridView().setAnimateChildLayout(false);
+        mRowsFragment.getVerticalGridView().setFocusSearchDisabled(true);
+        mHeadersFragment.getVerticalGridView().setFocusSearchDisabled(true);
+        createHeadersTransition(withHeaders);
+    }
+
+    private boolean isVerticalScrolling() {
+        // don't run transition
+        return mHeadersFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE
+                || mRowsFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            // If fastlane is disabled, just return null.
+            if (!mCanShowHeaders) return null;
+
+            // if fast lane is running transition,  focus stays
+            if (mHeadersTransition != null) return focused;
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+            if (direction == View.FOCUS_LEFT) {
+                if (isVerticalScrolling() || mShowingHeaders) {
+                    return focused;
+                }
+                return mHeadersFragment.getVerticalGridView();
+            } else if (direction == View.FOCUS_RIGHT) {
+                if (isVerticalScrolling() || !mShowingHeaders) {
+                    return focused;
+                }
+                return mRowsFragment.getVerticalGridView();
+            } else if (focused == mSearchOrbView && direction == View.FOCUS_DOWN) {
+                return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
+                    mRowsFragment.getVerticalGridView();
+
+            } else if (focused != mSearchOrbView && mSearchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return mSearchOrbView;
+
+            } else {
+                return null;
+            }
+        }
+    };
+
+    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+            new BrowseFrameLayout.OnChildFocusListener() {
+        @Override
+        public void onRequestChildFocus(View child, View focused) {
+            int childId = child.getId();
+            if (mHeadersTransition != null) return;
+            if (childId == R.id.browse_container_dock && mShowingHeaders) {
+                mShowingHeaders = false;
+                onHeadersTransitionStart(false);
+                mTransitionHelper.runTransition(mSceneWithoutHeaders, mHeadersTransition);
+            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+                mShowingHeaders = true;
+                //mHeadersFragment.getView().setAlpha(1f);
+                onHeadersTransitionStart(true);
+                mTransitionHelper.runTransition(mSceneWithHeaders, mHeadersTransition);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
+        mContainerListMarginLeft = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
+        mContainerListAlignTop = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
+        ta.recycle();
+        mHeadersTransitionStartDelay = getResources()
+                .getInteger(R.integer.lb_browse_headers_transition_delay);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+            mRowsFragment = new RowsFragment();
+            mHeadersFragment = new HeadersFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersFragment)
+                    .replace(R.id.browse_container_dock, mRowsFragment).commit();
+        } else {
+            mHeadersFragment = (HeadersFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_headers_dock);
+            mRowsFragment = (RowsFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_container_dock);
+        }
+        mRowsFragment.setAdapter(mAdapter);
+        mHeadersFragment.setAdapter(mAdapter);
+
+        mRowsFragment.setOnItemSelectedListener(mRowSelectedListener);
+        mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener);
+        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
+
+        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        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);
+        if (mExternalOnSearchClickedListener != null) {
+            mSearchOrbView.setOnOrbClickedListener(mExternalOnSearchClickedListener);
+        }
+
+        readArguments(getArguments());
+        if (mParams != null) {
+            setBadgeDrawable(mParams.mBadgeDrawable);
+            setTitle(mParams.mTitle);
+            setHeadersState(mParams.mHeadersState);
+        }
+
+        mTransitionHelper = new TransitionHelper(getActivity());
+        mSceneWithTitle = mTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showTitle(true);
+            }
+        });
+        mSceneWithoutTitle = mTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showTitle(false);
+            }
+        });
+        mSceneWithHeaders = mTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(true);
+            }
+        });
+        mSceneWithoutHeaders =  mTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(false);
+            }
+        });
+        mTitleTransition = mTransitionHelper.createAutoTransition();
+        mTransitionHelper.excludeChildren(mTitleTransition, R.id.browse_headers, true);
+        mTransitionHelper.excludeChildren(mTitleTransition, R.id.container_list, true);
+
+        return root;
+    }
+
+    private void createHeadersTransition(boolean withHeaders) {
+        ArrayList<View> fastHeaders = new ArrayList<View>();
+        ArrayList<Integer> fastHeaderPositions = new ArrayList<Integer>();
+        ArrayList<View> headers = new ArrayList<View>();
+        ArrayList<Integer> headerPositions = new ArrayList<Integer>();
+
+        mHeadersFragment.getHeaderViews(fastHeaders, fastHeaderPositions);
+        mRowsFragment.getHeaderViews(headers, headerPositions);
+
+        mHeadersTransition = mTransitionHelper.createTransitionSet(true);
+        mTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
+        Object changeBounds = mTransitionHelper.createChangeBounds(true);
+        Object fadeIn = mTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
+        Object fadeOut = mTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
+        if (!withHeaders) {
+            mTransitionHelper.setChangeBoundsDefaultStartDelay(changeBounds,
+                    mHeadersTransitionStartDelay);
+        }
+
+        for (int i = 0; i < headerPositions.size(); i++) {
+            Integer position = headerPositions.get(i);
+            if (position == mSelectedPosition) {
+                headers.get(i).setId(sReparentHeaderId);
+                mTransitionHelper.setChangeBoundsStartDelay(changeBounds, sReparentHeaderId,
+                        withHeaders ? mHeadersTransitionStartDelay : 0);
+                mTransitionHelper.exclude(fadeIn, headers.get(i), true);
+                mTransitionHelper.exclude(fadeOut, headers.get(i), true);
+            } else {
+                headers.get(i).setId(View.NO_ID);
+            }
+        }
+        for (int i = 0; i < fastHeaderPositions.size(); i++) {
+            Integer position = fastHeaderPositions.get(i);
+            if (position == mSelectedPosition) {
+                fastHeaders.get(i).setId(sReparentHeaderId);
+                mTransitionHelper.setChangeBoundsStartDelay(changeBounds, sReparentHeaderId,
+                        withHeaders ? mHeadersTransitionStartDelay : 0);
+                mTransitionHelper.exclude(fadeIn, fastHeaders.get(i), true);
+                mTransitionHelper.exclude(fadeOut, fastHeaders.get(i), true);
+            } else {
+                fastHeaders.get(i).setId(View.NO_ID);
+            }
+        }
+
+        mTransitionHelper.addTransition(mHeadersTransition, fadeOut);
+        mTransitionHelper.addTransition(mHeadersTransition, changeBounds);
+        mTransitionHelper.addTransition(mHeadersTransition, fadeIn);
+
+        mTransitionHelper.setTransitionCompleteListener(mHeadersTransition, new Runnable() {
+            @Override
+            public void run() {
+                mHeadersTransition = null;
+                // TODO: deal fragment destroy view properly
+                VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
+                if (rowsGridView != null) {
+                    rowsGridView.setAnimateChildLayout(true);
+                    rowsGridView.setFocusSearchDisabled(false);
+                    if (!mShowingHeaders && !rowsGridView.hasFocus()) {
+                        rowsGridView.requestFocus();
+                    }
+                }
+                VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
+                if (headerGridView != null) {
+                    headerGridView.setFocusSearchDisabled(false);
+                    headerGridView.invalidate();
+                    if (mShowingHeaders && !headerGridView.hasFocus()) {
+                        headerGridView.requestFocus();
+                    }
+                }
+            }
+        });
+    }
+
+    private void showTitle(boolean show) {
+        mBrowseTitle.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    private void showHeaders(boolean show) {
+        if (DEBUG) Log.v(TAG, "showHeaders " + show);
+        mHeadersFragment.setHeadersVisiblity(show);
+
+        View containerList = mRowsFragment.getView();
+        MarginLayoutParams lp;
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.leftMargin = show ? mContainerListMarginLeft : 0;
+        containerList.setLayoutParams(lp);
+        mRowsFragment.setExpand(!show);
+    }
+
+    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
+        new HeadersFragment.OnHeaderClickedListener() {
+            @Override
+            public void onHeaderClicked() {
+                if (!mCanShowHeaders || !mShowingHeaders) return;
+
+                if (mHeadersTransition != null) {
+                    return;
+                }
+                mShowingHeaders = false;
+                onHeadersTransitionStart(false);
+                mTransitionHelper.runTransition(mSceneWithoutHeaders, mHeadersTransition);
+                mRowsFragment.getVerticalGridView().requestFocus();
+            }
+        };
+
+    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mExternalOnItemSelectedListener != null) {
+                mExternalOnItemSelectedListener.onItemSelected(item, row);
+            }
+        }
+    };
+
+    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            int position = mHeadersFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "header selected position " + position);
+            onRowSelected(position);
+        }
+    };
+
+    private void onRowSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSetSelectionRunnable.mPosition = position;
+            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+
+            if (position == 0) {
+                if (!mShowingTitle) {
+                    mTransitionHelper.runTransition(mSceneWithTitle, mTitleTransition);
+                    mShowingTitle = true;
+                }
+            } else if (mShowingTitle) {
+                mTransitionHelper.runTransition(mSceneWithoutTitle, mTitleTransition);
+                mShowingTitle = false;
+            }
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        @Override
+        public void run() {
+            setSelection(mPosition);
+        }
+    }
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    private void setSelection(int position) {
+        if (position != NO_POSITION) {
+            mRowsFragment.setSelectedPosition(position);
+            mHeadersFragment.setSelectedPosition(position);
+        }
+        mSelectedPosition = position;
+    }
+
+    private void setVerticalVerticalGridViewLayout(VerticalGridView listview, int extraOffset) {
+        // align the top edge of item to a fixed position
+        listview.setItemAlignmentOffset(0);
+        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignmentOffset(mContainerListAlignTop + extraOffset);
+        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    /**
+     * Setup dimensions that are only meaningful when the child Fragments are inside
+     * BrowseFragment.
+     */
+    private void setupChildFragmentsLayout() {
+        VerticalGridView headerList = mHeadersFragment.getVerticalGridView();
+        VerticalGridView containerList = mRowsFragment.getVerticalGridView();
+
+        // Both fragments list view has the same alignment
+        setVerticalVerticalGridViewLayout(headerList, 16);
+        setVerticalVerticalGridViewLayout(containerList, 0);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupChildFragmentsLayout();
+        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
+            mHeadersFragment.getView().requestFocus();
+        } else if ((!mCanShowHeaders || !mShowingHeaders)
+                && mRowsFragment.getView() != null) {
+            mRowsFragment.getView().requestFocus();
+        }
+        showHeaders(mCanShowHeaders && mShowingHeaders);
+    }
+
+    private void readArguments(Bundle args) {
+        if (args == null) {
+            return;
+        }
+        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);
+        } else {
+            mBadgeView.setVisibility(View.GONE);
+        }
+    }
+
+    private void setTitle(String title) {
+        if (mTitleView != null) {
+            mTitleView.setText(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;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
new file mode 100644
index 0000000..4f04b05
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Top level implementation viewgroup for browse to manage transitions between
+ * browse sub fragments.
+ *
+ */
+class BrowseFrameLayout extends FrameLayout {
+
+    public interface OnFocusSearchListener {
+        public View onFocusSearch(View focused, int direction);
+    }
+
+    public interface OnChildFocusListener {
+        public void onRequestChildFocus(View child, View focused);
+    }
+
+    public BrowseFrameLayout(Context context) {
+        this(context, null, 0);
+    }
+
+    public BrowseFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BrowseFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    private OnFocusSearchListener mListener;
+    private OnChildFocusListener mOnChildFocusListener;
+
+    public void setOnFocusSearchListener(OnFocusSearchListener listener) {
+        mListener = listener;
+    }
+
+    public void setOnChildFocusListener(OnChildFocusListener listener) {
+        mOnChildFocusListener = listener;
+    }
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        if (mListener != null) {
+            View view = mListener.onFocusSearch(focused, direction);
+            if (view != null) {
+                return view;
+            }
+        }
+        return super.focusSearch(focused, direction);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (mOnChildFocusListener != null) {
+            mOnChildFocusListener.onRequestChildFocus(child, focused);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
new file mode 100644
index 0000000..3f10a63
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Customized FrameLayout excludes margin of child from calculating the child size.
+ * So we can change left margin of rows while keep the width of rows unchanged without
+ * using hardcoded DIPS.
+ */
+class BrowseRowsFrameLayout extends FrameLayout {
+
+    public BrowseRowsFrameLayout(Context context) {
+        this(context ,null);
+    }
+
+    public BrowseRowsFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BrowseRowsFrameLayout(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                getPaddingLeft() + getPaddingRight() + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                getPaddingTop() + getPaddingBottom() + heightUsed, lp.height);
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
new file mode 100644
index 0000000..9f426d3
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -0,0 +1,144 @@
+/*
+ * 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.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;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Wrapper fragment for leanback details screens.
+ */
+public class DetailsFragment extends Fragment {
+    private static final String TAG = "DetailsFragment";
+    private static boolean DEBUG = false;
+
+    private RowsFragment mRowsFragment;
+
+    private ObjectAdapter mAdapter;
+    private int mContainerListAlignTop;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private int mSelectedPosition = -1;
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item Clicked listener.
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mContainerListAlignTop =
+            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
+        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
+                R.id.fragment_dock); 
+        if (mRowsFragment == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.fragment_dock, mRowsFragment).commit();
+        }
+        mRowsFragment.setAdapter(mAdapter);
+        mRowsFragment.setOnItemSelectedListener(mRowSelectedListener);
+        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
+        return view;
+    }
+
+    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            if (mExternalOnItemSelectedListener != null) {
+                mExternalOnItemSelectedListener.onItemSelected(item, row);
+            }
+        }
+    };
+
+    private void setVerticalGridViewLayout(VerticalGridView listview) {
+        // align the top edge of item to a fixed position
+        listview.setItemAlignmentOffset(0);
+        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignmentOffset(mContainerListAlignTop);
+        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    /**
+     * Setup dimensions that are only meaningful when the child Fragments are inside
+     * DetailsFragment.
+     */
+    private void setupChildFragmentLayout() {
+        VerticalGridView containerList = mRowsFragment.getVerticalGridView();
+        setVerticalGridViewLayout(containerList);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupChildFragmentLayout();
+        mRowsFragment.getView().requestFocus();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
new file mode 100644
index 0000000..7cb5ce1
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.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.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * An internal fragment containing a list of row headers.
+ */
+public class HeadersFragment extends BaseRowFragment {
+
+    interface OnHeaderClickedListener {
+        void onHeaderClicked();
+    }
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnHeaderClickedListener mOnHeaderClickedListener;
+    private boolean mShow = true;
+
+    private static final Presenter sHeaderPresenter = new RowHeaderPresenter();
+
+    public HeadersFragment() {
+        setPresenterSelector(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object item) {
+                return sHeaderPresenter;
+            }
+        });
+    }
+
+    public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
+        mOnHeaderClickedListener = listener;
+    }
+
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    @Override
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        if (mOnItemSelectedListener != null) {
+            if (position >= 0) {
+                Row row = (Row) getAdapter().get(position);
+                mOnItemSelectedListener.onItemSelected(null, row);
+            }
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+            View headerView = viewHolder.getViewHolder().view;
+            headerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnHeaderClickedListener != null) {
+                        mOnHeaderClickedListener.onHeaderClicked();
+                    }
+                }
+            });
+            headerView.setFocusable(true);
+            headerView.setFocusableInTouchMode(true);
+        }
+
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+            View headerView = viewHolder.getViewHolder().view;
+            headerView.setVisibility(mShow ? View.VISIBLE : View.INVISIBLE);
+        }
+    };
+
+    @Override
+    protected int getLayoutResourceId() {
+        return R.layout.lb_headers_fragment;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        if (getBridgeAdapter() != null && getVerticalGridView() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
+        }
+    }
+
+    void getHeaderViews(List<View> headers, List<Integer> positions) {
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        final int count = listView.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = listView.getChildAt(i);
+            headers.add(child);
+            positions.add(listView.getChildViewHolder(child).getPosition());
+        }
+    }
+
+    void setHeadersVisiblity(boolean show) {
+        mShow = show;
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        final int count = listView.getChildCount();
+        final int visibility = mShow ? View.VISIBLE : View.INVISIBLE;
+
+        // we should set visibility of selected view first so that it can
+        // regain the focus from parent (which is FOCUS_AFTER_DESCENDANT)
+        final int selectedPosition = listView.getSelectedPosition();
+        if (selectedPosition >= 0) {
+            RecyclerView.ViewHolder vh = listView.findViewHolderForPosition(selectedPosition);
+            if (vh != null) {
+                vh.itemView.setVisibility(visibility);
+            }
+        }
+        for (int i = 0; i < count; i++) {
+            View child = listView.getChildAt(i);
+            if (listView.getChildPosition(child) != selectedPosition) {
+                child.setVisibility(visibility);
+            }
+        }
+    }
+
+    @Override
+    protected void updateAdapter() {
+        super.updateAdapter();
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mAdapterListener);
+        }
+        if (adapter != null && getVerticalGridView() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
new file mode 100644
index 0000000..a97c5d8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.graphics.Canvas;
+import android.os.Bundle;
+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.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import java.util.List;
+
+/**
+ * An ordered set of rows of leanback widgets.
+ */
+public class RowsFragment extends BaseRowFragment {
+
+    /**
+     * Internal helper class that manages row select animation and apply a default
+     * dim to each row.
+     */
+    final class RowViewHolderExtra implements TimeListener {
+        final RowPresenter mRowPresenter;
+        final Presenter.ViewHolder mRowViewHolder;
+
+        final TimeAnimator mSelectAnimator = new TimeAnimator();
+        final ColorOverlayDimmer mColorDimmer;
+        int mSelectAnimatorDurationInUse;
+        Interpolator mSelectAnimatorInterpolatorInUse;
+        float mSelectLevelAnimStart;
+        float mSelectLevelAnimDelta;
+
+        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
+            mRowPresenter = (RowPresenter) ibvh.getPresenter();
+            mRowViewHolder = ibvh.getViewHolder();
+            mSelectAnimator.setTimeListener(this);
+            if (mRowPresenter.getSelectEffectEnabled()
+                    && mRowPresenter.isUsingDefaultSelectEffect()) {
+                mColorDimmer = ColorOverlayDimmer.createDefault(ibvh.itemView.getContext());
+            } else {
+                mColorDimmer = null;
+            }
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mSelectAnimatorDurationInUse) {
+                fraction = 1;
+                mSelectAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
+            }
+            if (mSelectAnimatorInterpolatorInUse != null) {
+                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
+            }
+            float level =  mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            if (mColorDimmer != null) {
+                mColorDimmer.setActiveLevel(level);
+            }
+            mRowPresenter.setSelectLevel(mRowViewHolder, level);
+        }
+
+        void animateSelect(boolean select, boolean immediate) {
+            endAnimation();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                mRowPresenter.setSelectLevel(mRowViewHolder, end);
+                if (mColorDimmer != null) {
+                    mColorDimmer.setActiveLevel(end);
+                }
+            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
+                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
+                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
+                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
+                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
+                mSelectAnimator.start();
+            }
+        }
+
+        void endAnimation() {
+            mSelectAnimator.end();
+        }
+
+        void drawDimForSelection(Canvas c) {
+            if (mColorDimmer != null) {
+                mColorDimmer.drawColorOverlay(c, mRowViewHolder.view, false);
+            }
+        }
+    }
+
+    private static final String TAG = "RowsFragment";
+    private static final boolean DEBUG = false;
+
+    private ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
+    private boolean mExpand = true;
+    private boolean mViewsCreated;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+
+    // Select animation and interpolator are not intended to exposed at this moment.
+    // They might be synced with vertical scroll animation later.
+    int mSelectAnimatorDuration;
+    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
+
+    /**
+     * 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.
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Set the visibility of titles/hovercard of browse rows.
+     */
+    public void setExpand(boolean expand) {
+        mExpand = expand;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            listView.setActivated(expand);
+            final int count = listView.getChildCount();
+            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+                setRowViewExpanded(vh, mExpand);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                setOnItemSelectedListener(vh, mOnItemSelectedListener);
+            }
+        }
+    }
+
+    @Override
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        ItemBridgeAdapter.ViewHolder vh = (view == null) ? null :
+            (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+
+        if (mSelectedViewHolder != vh) {
+            if (DEBUG) Log.v(TAG, "new row selected position " + position + " view " + view);
+
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, false, false);
+            }
+            mSelectedViewHolder = vh;
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, true, false);
+            }
+        }
+    }
+
+    @Override
+    protected int getLayoutResourceId() {
+        return R.layout.lb_rows_fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSelectAnimatorDuration = getResources().getInteger(R.integer.lb_browse_rows_anim_duration);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onViewCreated");
+        super.onViewCreated(view, savedInstanceState);
+        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
+        getVerticalGridView().addItemDecoration(mItemDecoration);
+    }
+
+    private RecyclerView.ItemDecoration mItemDecoration = new RecyclerView.ItemDecoration() {
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent) {
+            final int count = parent.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                        parent.getViewHolderForChildAt(i);
+                RowViewHolderExtra extra = (RowViewHolderExtra) ibvh.getExtraObject();
+                extra.drawDimForSelection(c);
+            }
+        }
+    };
+
+    private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
+        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
+    }
+
+    private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
+            boolean immediate) {
+        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+        extra.animateSelect(selected, immediate);
+        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
+    }
+
+    private static void setOnItemSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+            OnItemSelectedListener listener) {
+        ((RowPresenter) vh.getPresenter()).setOnItemSelectedListener(listener);
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onAddPresenter(Presenter presenter) {
+            ((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);
+            }
+            mViewsCreated = true;
+            vh.setExtraObject(new RowViewHolderExtra(vh));
+            // selected state is initialized to false, then driven by grid view onChildSelected
+            // events.  When there is rebind, grid view fires onChildSelected event properly.
+            // So we don't need do anything special later in onBind or onAttachedToWindow.
+            setRowViewSelected(vh, false, true);
+        }
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachToWindow");
+            // All views share the same mExpand value.  When we attach a view to grid view,
+            // we should make sure it pick up the latest mExpand value we set early on other
+            // attached views.  For no-structure-change update,  the view is rebound to new data,
+            // but again it should use the unchanged mExpand value,  so we don't need do any
+            // thing in onBind.
+            setRowViewExpanded(vh, mExpand);
+            setOnItemSelectedListener(vh, mOnItemSelectedListener);
+        }
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
+            RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+            extra.endAnimation();
+        }
+    };
+
+    @Override
+    protected void updateAdapter() {
+        super.updateAdapter();
+        mSelectedViewHolder = null;
+        mViewsCreated = false;
+
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mBridgeAdapterListener);
+        }
+    }
+
+    void getHeaderViews(List<View> headers, List<Integer> positions) {
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        final int count = listView.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = listView.getChildAt(i);
+            ItemBridgeAdapter.ViewHolder viewHolder = (ItemBridgeAdapter.ViewHolder)
+                    listView.getChildViewHolder(child);
+            RowPresenter presenter = (RowPresenter) viewHolder.getPresenter();
+            RowPresenter.ViewHolder rowViewHolder = presenter.getRowViewHolder(
+                    viewHolder.getViewHolder());
+            headers.add(rowViewHolder.getHeaderViewHolder().view);
+            positions.add(viewHolder.getPosition());
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
new file mode 100644
index 0000000..9720634
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ObjectAdapter;
+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.SearchBar;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.support.v17.leanback.R;
+
+/**
+ * A fragment to handle searches
+ */
+public class SearchFragment extends Fragment {
+    private static final String TAG = SearchFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final String ARG_QUERY = SearchFragment.class.getCanonicalName() + ".query";
+
+    /**
+     * Search API exposed to application
+     */
+    public static interface SearchResultProvider {
+        /**
+         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
+         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
+         *
+         * <p>As results are retrieved, the application should use the data set notification methods
+         * on the ObjectAdapter to instruct the SearchFragment to update the results.</p>
+         *
+         * @return ObjectAdapter The result object adapter.
+         */
+        public ObjectAdapter getResultsAdapter();
+
+        /**
+         * <p>Method invoked when the search query is updated.</p>
+         *
+         * <p>This is called as soon as the query changes; it is up to the application to add a
+         * delay before actually executing the queries if needed.</p>
+         *
+         * @param newQuery The current search query.
+         * @return whether the results changed or not.
+         */
+        public boolean onQueryTextChange(String newQuery);
+
+        /**
+         * Method invoked when the search query is submitted, either by dismissing the keyboard,
+         * pressing search or next on the keyboard or when voice has detected the end of the query.
+         *
+         * @param query The query.
+         * @return whether the results changed or not
+         */
+        public boolean onQueryTextSubmit(String query);
+    }
+
+    private RowsFragment mRowsFragment;
+    private final Handler mHandler = new Handler();
+
+    private SearchBar mSearchBar;
+    private SearchResultProvider mProvider;
+    private String mPendingQuery = null;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private ObjectAdapter mResultAdapter;
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String query) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_QUERY, query);
+        return args;
+    }
+
+    /**
+     * Create a search fragment with a given search query to start with
+     *
+     * You should only use this if you need to start the search fragment with a pre-filled query
+     *
+     * @param query the search query to start with
+     * @return a new SearchFragment
+     */
+    public static SearchFragment newInstance(String query) {
+        SearchFragment fragment = new SearchFragment();
+        Bundle args = createArgs(null, query);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
+        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
+        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+            @Override
+            public void onSearchQueryChange(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s", query));
+                if (null != mProvider) {
+                    retrieveResults(query);
+                } else {
+                    mPendingQuery = query;
+                }
+            }
+
+            @Override
+            public void onSearchQuerySubmit(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
+                mRowsFragment.setSelectedPosition(0);
+                mRowsFragment.getVerticalGridView().requestFocus();
+                if (null != mProvider) {
+                    mProvider.onQueryTextSubmit(query);
+                }
+            }
+
+            @Override
+            public void onKeyboardDismiss(String query) {
+                mRowsFragment.setSelectedPosition(0);
+                mRowsFragment.getVerticalGridView().requestFocus();
+            }
+        });
+
+        Bundle args = getArguments();
+        if (null != args) {
+            String query = args.getString(ARG_QUERY, "");
+            mSearchBar.setSearchQuery(query);
+        }
+
+        // Inject the RowsFragment in the results container
+        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.lb_results_frame, mRowsFragment).commit();
+        } else {
+            mRowsFragment = (RowsFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_container_dock);
+        }
+        mRowsFragment.setOnItemSelectedListener(new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(Object item, Row row) {
+                int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+                if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
+                mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+                if (null != mOnItemSelectedListener) {
+                    mOnItemSelectedListener.onItemSelected(item, row);
+                }
+            }
+        });
+        mRowsFragment.setOnItemClickedListener(new OnItemClickedListener() {
+            @Override
+            public void onItemClicked(Object item, Row row) {
+                if (null != mOnItemClickedListener) {
+                    mOnItemClickedListener.onItemClicked(item, row);
+                }
+            }
+        });
+        mRowsFragment.setExpand(true);
+        if (null != mProvider) {
+            onSetSearchResultProvider();
+        }
+        return root;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        VerticalGridView list = mRowsFragment.getVerticalGridView();
+        int mContainerListAlignTop =
+                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
+        list.setItemAlignmentOffset(0);
+        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignmentOffset(mContainerListAlignTop);
+        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    /**
+     * Set the search provider, which is responsible for returning items given
+     * a search term
+     *
+     * @param searchResultProvider the search provider
+     */
+    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+        mProvider = searchResultProvider;
+        onSetSearchResultProvider();
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @param listener the item selection listener
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    private void retrieveResults(String searchQuery) {
+        if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
+        mProvider.onQueryTextChange(searchQuery);
+    }
+
+    private void onSetSearchResultProvider() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Retrieve the result adapter
+                mResultAdapter = mProvider.getResultsAdapter();
+                if (null != mRowsFragment) {
+                    mRowsFragment.setAdapter(mResultAdapter);
+                    executePendingQuery();
+                }
+            }
+        });
+    }
+
+    private void executePendingQuery() {
+        if (null != mPendingQuery && null != mResultAdapter) {
+            String query = mPendingQuery;
+            mPendingQuery = null;
+            retrieveResults(query);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/TransitionHelper.java
new file mode 100644
index 0000000..78c2766
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/TransitionHelper.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Helper for view transitions.
+ */
+final class TransitionHelper {
+
+    public static final int FADE_IN = 0x1;
+    public static final int FADE_OUT = 0x2;
+
+    TransitionHelperVersionImpl mImpl;
+
+    /**
+     * Gets whether the system supports Transition animations.
+     *
+     * @return True if Transition animations are supported.
+     */
+    public static boolean systemSupportsTransitions() {
+        if (Build.VERSION.SDK_INT >= 19) {
+            // Supported on Android 4.4 or later.
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Interface implemented by classes that support Transition animations.
+     */
+    static interface TransitionHelperVersionImpl {
+
+        public Object createScene(ViewGroup sceneRoot, Runnable r);
+
+        public Object createAutoTransition();
+
+        public Object createFadeTransition(int fadingMode);
+
+        public Object createChangeBounds(boolean reparent);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay);
+
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay);
+
+        public Object createTransitionSet(boolean sequential);
+
+        public void addTransition(Object transitionSet, Object transition);
+
+        public void setTransitionCompleteListener(Object transition, Runnable listener);
+
+        public void runTransition(Object scene, Object transition);
+
+        public void exclude(Object transition, int targetId, boolean exclude);
+
+        public void exclude(Object transition, View targetView, boolean exclude);
+
+        public void excludeChildren(Object transition, int targetId, boolean exclude);
+
+        public void excludeChildren(Object transition, View target, boolean exclude);
+
+        public void include(Object transition, int targetId);
+
+        public void include(Object transition, View targetView);
+
+    }
+
+    /**
+     * Interface used when we do not support Transition animations.
+     */
+    private static final class TransitionHelperStubImpl implements TransitionHelperVersionImpl {
+
+        private static class TransitionStub {
+            Runnable mCompleteListener;
+        }
+
+        @Override
+        public Object createScene(ViewGroup sceneRoot, Runnable r) {
+            return r;
+        }
+
+        @Override
+        public Object createAutoTransition() {
+            return new TransitionStub();
+        }
+
+        @Override
+        public Object createFadeTransition(int fadingMode) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public Object createChangeBounds(boolean reparent) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        }
+
+        @Override
+        public Object createTransitionSet(boolean sequential) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public void addTransition(Object transitionSet, Object transition) {
+        }
+
+        @Override
+        public void exclude(Object transition, int targetId, boolean exclude) {
+        }
+
+        @Override
+        public void exclude(Object transition, View targetView, boolean exclude) {
+        }
+
+        @Override
+        public void excludeChildren(Object transition, int targetId, boolean exclude) {
+        }
+
+        @Override
+        public void excludeChildren(Object transition, View targetView, boolean exclude) {
+        }
+
+        @Override
+        public void include(Object transition, int targetId) {
+        }
+
+        @Override
+        public void include(Object transition, View targetView) {
+        }
+
+        @Override
+        public void setTransitionCompleteListener(Object transition, Runnable listener) {
+            ((TransitionStub) transition).mCompleteListener = listener;
+        }
+
+        @Override
+        public void runTransition(Object scene, Object transition) {
+            Runnable r = ((Runnable) scene);
+            if (r != null) {
+                r.run();
+            }
+            TransitionStub transitionStub = (TransitionStub) transition;
+            if (transitionStub != null && transitionStub.mCompleteListener != null) {
+                transitionStub.mCompleteListener.run();
+            }
+        }
+    }
+
+    /**
+     * Implementation used on KitKat (and above).
+     */
+    private static final class TransitionHelperKitkatImpl implements TransitionHelperVersionImpl {
+        private final TransitionHelperKitkat mTransitionHelper;
+
+        TransitionHelperKitkatImpl(Context context) {
+            mTransitionHelper = new TransitionHelperKitkat(context);
+        }
+
+        @Override
+        public Object createScene(ViewGroup sceneRoot, Runnable r) {
+            return mTransitionHelper.createScene(sceneRoot, r);
+        }
+
+        @Override
+        public Object createAutoTransition() {
+            return mTransitionHelper.createAutoTransition();
+        }
+
+        @Override
+        public Object createFadeTransition(int fadingMode) {
+            return mTransitionHelper.createFadeTransition(fadingMode);
+        }
+
+        @Override
+        public Object createChangeBounds(boolean reparent) {
+            return mTransitionHelper.createChangeBounds(reparent);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, view, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, viewId, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, className, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+            mTransitionHelper.setChangeBoundsDefaultStartDelay(changeBounds, startDelay);
+        }
+
+        @Override
+        public Object createTransitionSet(boolean sequential) {
+            return mTransitionHelper.createTransitionSet(sequential);
+        }
+
+        @Override
+        public void addTransition(Object transitionSet, Object transition) {
+            mTransitionHelper.addTransition(transitionSet, transition);
+        }
+
+        @Override
+        public void exclude(Object transition, int targetId, boolean exclude) {
+            mTransitionHelper.exclude(transition, targetId, exclude);
+        }
+
+        @Override
+        public void exclude(Object transition, View targetView, boolean exclude) {
+            mTransitionHelper.exclude(transition, targetView, exclude);
+        }
+
+        @Override
+        public void excludeChildren(Object transition, int targetId, boolean exclude) {
+            mTransitionHelper.excludeChildren(transition, targetId, exclude);
+        }
+
+        @Override
+        public void excludeChildren(Object transition, View targetView, boolean exclude) {
+            mTransitionHelper.excludeChildren(transition, targetView, exclude);
+        }
+
+        @Override
+        public void include(Object transition, int targetId) {
+            mTransitionHelper.include(transition, targetId);
+        }
+
+        @Override
+        public void include(Object transition, View targetView) {
+            mTransitionHelper.include(transition, targetView);
+        }
+
+        @Override
+        public void setTransitionCompleteListener(Object transition, Runnable listener) {
+            mTransitionHelper.setTransitionCompleteListener(transition, listener);
+        }
+
+        @Override
+        public void runTransition(Object scene, Object transition) {
+            mTransitionHelper.runTransition(scene, transition);
+        }
+    }
+
+    /**
+     * Returns the TransitionHelper that can be used to perform Transition
+     * animations.
+     *
+     * @param context A context for accessing system resources.
+     */
+    public TransitionHelper(Context context) {
+        if (systemSupportsTransitions()) {
+            mImpl = new TransitionHelperKitkatImpl(context);
+        } else {
+            mImpl = new TransitionHelperStubImpl();
+        }
+    }
+
+    public Object createScene(ViewGroup sceneRoot, Runnable r) {
+        return mImpl.createScene(sceneRoot, r);
+    }
+
+    public Object createChangeBounds(boolean reparent) {
+        return mImpl.createChangeBounds(reparent);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, view, startDelay);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, viewId, startDelay);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, String className, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, className, startDelay);
+    }
+
+    public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        mImpl.setChangeBoundsDefaultStartDelay(changeBounds, startDelay);
+    }
+
+    public Object createTransitionSet(boolean sequential) {
+        return mImpl.createTransitionSet(sequential);
+    }
+
+    public void addTransition(Object transitionSet, Object transition) {
+        mImpl.addTransition(transitionSet, transition);
+    }
+
+    public void exclude(Object transition, int targetId, boolean exclude) {
+        mImpl.exclude(transition, targetId, exclude);
+    }
+
+    public void exclude(Object transition, View targetView, boolean exclude) {
+        mImpl.exclude(transition, targetView, exclude);
+    }
+
+    public void excludeChildren(Object transition, int targetId, boolean exclude) {
+        mImpl.excludeChildren(transition, targetId, exclude);
+    }
+
+    public void excludeChildren(Object transition, View targetView, boolean exclude) {
+        mImpl.excludeChildren(transition, targetView, exclude);
+    }
+
+    public void include(Object transition, int targetId) {
+        mImpl.include(transition, targetId);
+    }
+
+    public void include(Object transition, View targetView) {
+        mImpl.include(transition, targetView);
+    }
+
+    public Object createAutoTransition() {
+        return mImpl.createAutoTransition();
+    }
+
+    public Object createFadeTransition(int fadeMode) {
+        return mImpl.createFadeTransition(fadeMode);
+    }
+
+    public void setTransitionCompleteListener(Object transition, Runnable listener) {
+        mImpl.setTransitionCompleteListener(transition, listener);
+    }
+
+    public void runTransition(Object scene, Object transition) {
+        mImpl.runTransition(scene, transition);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
new file mode 100644
index 0000000..ff4cddf
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -0,0 +1,271 @@
+/*
+ * 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.R;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.Log;
+import android.app.Fragment;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Leanback fragment for a vertical grid.
+ *
+ * Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
+ * an {@link ObjectAdapter}.
+ */
+public class VerticalGridFragment extends Fragment {
+    private static final String TAG = "VerticalGridFragment";
+    private static boolean DEBUG = false;
+
+    private Params mParams;
+    private ObjectAdapter mAdapter;
+    private VerticalGridPresenter mGridPresenter;
+    private VerticalGridPresenter.ViewHolder mGridViewHolder;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private View.OnClickListener mExternalOnSearchClickedListener;
+    private int mSelectedPosition = -1;
+
+    private ImageView mBadgeView;
+    private TextView mTitleView;
+    private ViewGroup mBrowseTitle;
+    private SearchOrbView mSearchOrbView;
+
+    public static class Params {
+        private String mTitle;
+        private Drawable mBadgeDrawable;
+
+        /**
+         * Sets the badge image.
+         */
+        public void setBadgeImage(Drawable drawable) {
+            mBadgeDrawable = drawable;
+        }
+
+        /**
+         * Returns the badge image.
+         */
+        public Drawable getBadgeImage() {
+            return mBadgeDrawable;
+        }
+
+        /**
+         * Sets a title for the browse fragment.
+         */
+        public void setTitle(String title) {
+            mTitle = title;
+        }
+
+        /**
+         * Returns the title for the browse fragment.
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+    }
+
+    /**
+     * Set fragment parameters.
+     */
+    public void setParams(Params params) {
+        mParams = params;
+        setBadgeDrawable(mParams.mBadgeDrawable);
+        setTitle(mParams.mTitle);
+    }
+
+    /**
+     * Returns fragment parameters.
+     */
+    public Params getParams() {
+        return mParams;
+    }
+
+    /**
+     * Set the grid presenter.
+     */
+    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+        if (gridPresenter == null) {
+            throw new IllegalArgumentException("Grid presenter may not be null");
+        }
+        mGridPresenter = gridPresenter;
+        if (mOnItemSelectedListener != null) {
+            mGridPresenter.setOnItemSelectedListener(mOnItemSelectedListener);
+        }
+        if (mOnItemClickedListener != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the grid presenter.
+     */
+    public VerticalGridPresenter getGridPresenter() {
+        return mGridPresenter;
+    }
+
+    /**
+     * Sets the object adapter for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the object adapter.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemSelectedListener(mOnItemSelectedListener);
+        }
+    }
+
+    // TODO: getitemselectedlistener?
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * 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.
+     *
+     * The listener onClick method will be invoked when the user click on the search action.
+     *
+     * @param listener The listener.
+     */
+    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);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = 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);
+        }
+
+        if (mParams != null) {
+            setBadgeDrawable(mParams.mBadgeDrawable);
+            setTitle(mParams.mTitle);
+        }
+
+        return root;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
+        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+        gridDock.addView(mGridViewHolder.view);
+
+        updateAdapter();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mGridViewHolder.getGridView().requestFocus();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mGridViewHolder = null;
+    }
+
+    /**
+     * Sets the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+        }
+    }
+
+    private void updateAdapter() {
+        if (mGridViewHolder != null) {
+            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+            if (mSelectedPosition != -1) {
+                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java b/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java
new file mode 100644
index 0000000..20b4a36
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.database;
+
+import android.database.Cursor;
+
+/**
+ * Abstract class used to convert the current {@link Cursor} row to a single
+ * object.
+ */
+public abstract class CursorMapper {
+
+    private Cursor mCursor;
+
+    /**
+     * Called once when the associated {@link Cursor} is changed. A subclass
+     * should bind column indexes to column names in this method. This method is
+     * not intended to be called outside of CursorMapper.
+     */
+    protected abstract void bindColumns(Cursor cursor);
+
+    /**
+     * A subclass should implement this method to create a single object using
+     * binding information. This method is not intended to be called
+     * outside of CursorMapper.
+     */
+    protected abstract Object bind(Cursor cursor);
+
+    /**
+     * Convert a {@link Cursor} at its current position to an Object.
+     */
+    public Object convert(Cursor cursor) {
+        if (cursor != mCursor) {
+            mCursor = cursor;
+            bindColumns(mCursor);
+        }
+        return bind(mCursor);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
new file mode 100644
index 0000000..872d282
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
@@ -0,0 +1,76 @@
+/*
+ * 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.graphics;
+
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.SparseArray;
+
+/**
+ * Cache of {@link ColorFilter}s for a given color at different alpha levels.
+ */
+public final class ColorFilterCache {
+
+    private static final SparseArray<ColorFilterCache> sColorToFiltersMap =
+            new SparseArray<ColorFilterCache>();
+
+    private final PorterDuffColorFilter[] mFilters = new PorterDuffColorFilter[0x100];
+
+    /**
+     * Get a ColorDimmer for a given color.  Only the RGB values are used; the 
+     * alpha channel is ignored in color. Subsequent calls to this method
+     * with the same color value will return the same cache.
+     *
+     * @param color The color to use for the color filters.
+     * @return A cache of ColorFilters at different alpha levels for the color.
+     */
+    public static ColorFilterCache getColorFilterCache(int color) {
+        final int r = Color.red(color);
+        final int g = Color.green(color);
+        final int b = Color.blue(color);
+        color = Color.rgb(r, g, b);
+        ColorFilterCache filters = sColorToFiltersMap.get(color);
+        if (filters == null) {
+            filters = new ColorFilterCache(r, g, b);
+            sColorToFiltersMap.put(color, filters);
+        }
+        return filters;
+    }
+
+    private ColorFilterCache(int r, int g, int b) {
+        // Pre cache all 256 filter levels
+        for (int i = 0x00; i <= 0xFF; i++) {
+            int color = Color.argb(i, r, g, b);
+            mFilters[i] = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+        }
+    }
+
+    /**
+     * Returns a ColorFilter for a given alpha level between 0 and 1.0.
+     *
+     * @param level The alpha level the filter should apply.
+     * @return A ColorFilter at the alpha level for the color represented by the
+     *         cache.
+     */
+    public ColorFilter getFilterForLevel(float level) {
+        if (level >= 0 && level <= 1.0) {
+            int filterIndex = (int) (0xFF * level);
+            return mFilters[filterIndex];
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
new file mode 100644
index 0000000..d64a098
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
@@ -0,0 +1,125 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.view.View;
+import android.support.v17.leanback.R;
+
+/**
+ * Helper class for applying a dim level to a View.  The ColorFilterDimmer
+ * uses a ColorFilter in a Paint object to dim the view according to the
+ * currently active level.
+ */
+public final class ColorFilterDimmer {
+
+    private final ColorFilterCache mColorDimmer;
+
+    private final float mActiveLevel;
+    private final float mDimmedLevel;
+
+    private final Paint mPaint;
+    private ColorFilter mFilter;
+
+    /**
+     * Creates a default ColorFilterDimmer. Uses the default color and level for
+     * the dimmer.
+     *
+     * @param context A Context used to retrieve Resources.
+     * @return A ColorFilterDimmer with the default dim color and levels.
+     */
+    public static ColorFilterDimmer createDefault(Context context) {
+        return new ColorFilterDimmer(ColorFilterCache.getColorFilterCache(
+                context.getResources().getColor(R.color.lb_view_dim_mask_color)),
+                0, context.getResources().getFraction(R.dimen.lb_view_dimmed_level, 1, 1));
+    }
+
+    /**
+     * Creates a ColorFilterDimmer for the given color and levels..
+     *
+     * @param dimmer      The ColorFilterCache for dim color.
+     * @param activeLevel The level of dimming when the View is in its active
+     *                    state. Must be a float value between 0.0 and 1.0.
+     * @param dimmedLevel The level of dimming when the View is in its dimmed
+     *                    state. Must be a float value between 0.0 and 1.0.
+     */
+    public static ColorFilterDimmer create(ColorFilterCache dimmer,
+            float activeLevel, float dimmedLevel) {
+        return new ColorFilterDimmer(dimmer, activeLevel, dimmedLevel);
+    }
+
+    private ColorFilterDimmer(ColorFilterCache dimmer, float activeLevel, float dimmedLevel) {
+        mColorDimmer = dimmer;
+        if (activeLevel > 1.0f) activeLevel = 1.0f;
+        if (activeLevel < 0.0f) activeLevel = 0.0f;
+        if (dimmedLevel > 1.0f) dimmedLevel = 1.0f;
+        if (dimmedLevel < 0.0f) dimmedLevel = 0.0f;
+        mActiveLevel = activeLevel;
+        mDimmedLevel = dimmedLevel;
+        mPaint = new Paint();
+    }
+
+    /**
+     * Apply current the ColorFilter to a View. This method will set the
+     * hardware layer of the view when applying a filter, and remove it when not
+     * applying a filter.
+     *
+     * @param view The View to apply the ColorFilter to.
+     */
+    public void applyFilterToView(View view) {
+        if (mFilter != null) {
+            view.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
+        } else {
+            view.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+        // FIXME: Current framework has bug that not triggering invalidate when change layer
+        // paint.  Will add conditional sdk version check once bug is fixed in released
+        // framework.
+        view.invalidate();
+    }
+
+    /**
+     * Sets the active level of the dimmer. Updates the ColorFilter based on the
+     * level.
+     *
+     * @param level A float between 0 (fully dim) and 1 (fully active).
+     */
+    public void setActiveLevel(float level) {
+        if (level < 0.0f) level = 0.0f;
+        if (level > 1.0f) level = 1.0f;
+        mFilter = mColorDimmer.getFilterForLevel(
+                mDimmedLevel + level * (mActiveLevel - mDimmedLevel));
+        mPaint.setColorFilter(mFilter);
+    }
+
+    /**
+     * Gets the ColorFilter set to the current dim level.
+     *
+     * @return The current ColorFilter.
+     */
+    public ColorFilter getColorFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Gets the Paint object set to the current dim level.
+     *
+     * @return The current Paint object.
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
new file mode 100644
index 0000000..17a185b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
@@ -0,0 +1,157 @@
+/*
+ * 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.graphics;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.v17.leanback.R;
+import android.view.View;
+
+/**
+ * Helper class for assigning a dim color to Paint. It holds the alpha value for
+ * the current active level.
+ */
+public final class ColorOverlayDimmer {
+
+    private final float mActiveLevel;
+    private final float mDimmedLevel;
+
+    private final Paint mPaint;
+
+    private int mAlpha;
+    private float mAlphaFloat;
+
+    /**
+     * Creates a default ColorOverlayDimmer.
+     */
+    public static ColorOverlayDimmer createDefault(Context context) {
+        return new ColorOverlayDimmer(
+                context.getResources().getColor(R.color.lb_view_dim_mask_color), 0,
+                context.getResources().getFraction(R.dimen.lb_view_dimmed_level, 1, 1));
+    }
+
+    /**
+     * Creates a ColorOverlayDimmer for the given color and levels.
+     *
+     * @param dimColor    The color for fully dimmed. Only the RGB values are
+     *                    used; the alpha channel is ignored.
+     * @param activeLevel The level of dimming when the View is in its active
+     *                    state. Must be a float value between 0.0 and 1.0.
+     * @param dimmedLevel The level of dimming when the View is in its dimmed
+     *                    state. Must be a float value between 0.0 and 1.0.
+     */
+    public static ColorOverlayDimmer createColorOverlayDimmer(int dimColor, float activeLevel,
+            float dimmedLevel) {
+        return new ColorOverlayDimmer(dimColor, activeLevel, dimmedLevel);
+    }
+
+    private ColorOverlayDimmer(int dimColor, float activeLevel, float dimmedLevel) {
+        if (activeLevel > 1.0f) activeLevel = 1.0f;
+        if (activeLevel < 0.0f) activeLevel = 0.0f;
+        if (dimmedLevel > 1.0f) dimmedLevel = 1.0f;
+        if (dimmedLevel < 0.0f) dimmedLevel = 0.0f;
+        mPaint = new Paint();
+        dimColor = Color.rgb(Color.red(dimColor), Color.green(dimColor), Color.blue(dimColor));
+        mPaint.setColor(dimColor);
+        mActiveLevel = activeLevel;
+        mDimmedLevel = dimmedLevel;
+        setActiveLevel(1);
+    }
+
+    /**
+     * Sets the active level of the dimmer. Updates the alpha value based on the
+     * level.
+     *
+     * @param level A float between 0 (fully dim) and 1 (fully active).
+     */
+    public void setActiveLevel(float level) {
+        mAlphaFloat = (mDimmedLevel + level * (mActiveLevel - mDimmedLevel));
+        mAlpha = (int) (255 * mAlphaFloat);
+        mPaint.setAlpha(mAlpha);
+    }
+
+    /**
+     * Returns whether the dimmer needs to draw.
+     */
+    public boolean needsDraw() {
+        return mAlpha != 0;
+    }
+
+    /**
+     * Returns the alpha value for the dimmer.
+     */
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Returns the float value between 0 and 1 corresponding to alpha between
+     * 0 and 255.
+     */
+    public float getAlphaFloat() {
+        return mAlphaFloat;
+    }
+
+    /**
+     * Returns the Paint object set to the current alpha value.
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Change the RGB of the color according to current dim level. Maintains the
+     * alpha value of the color.
+     *
+     * @param color The color to apply the dim level to.
+     * @return A color with the RGB values adjusted by the alpha of the current
+     *         dim level.
+     */
+    public int applyToColor(int color) {
+        float f = 1 - mAlphaFloat;
+        return Color.argb(Color.alpha(color),
+                (int)(Color.red(color) * f),
+                (int)(Color.green(color) * f),
+                (int)(Color.blue(color) * f));
+    }
+
+    /**
+     * Draw a dim color overlay on top of a child View inside the canvas of
+     * the parent View.
+     *
+     * @param c Canvas of the parent View.
+     * @param v A child of the parent View.
+     * @param includePadding Set to true to draw overlay on padding area of the
+     *        View.
+     */
+    public void drawColorOverlay(Canvas c, View v, boolean includePadding) {
+        c.save();
+        float dx = v.getLeft() + v.getTranslationX();
+        float dy = v.getTop() + v.getTranslationY();
+        c.translate(dx, dy);
+        c.concat(v.getMatrix());
+        c.translate(-dx, -dy);
+        if (includePadding) {
+            c.drawRect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom(), mPaint);
+        } else {
+            c.drawRect(v.getLeft() + v.getPaddingLeft(),
+                    v.getTop() + v.getPaddingTop(),
+                    v.getRight() - v.getPaddingRight(),
+                    v.getBottom() - v.getPaddingBottom(), mPaint);
+        }
+        c.restore();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
new file mode 100644
index 0000000..c42f3e0
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * An abstract {@link Presenter} for rendering a detailed description of an
+ * item. Typically this Presenter will be used in a DetailsOveriewRowPresenter.
+ *
+ * <p>Subclasses will override {@link #onBindDescription} to implement the data
+ * binding for this Presenter.
+ */
+public abstract class AbstractDetailsDescriptionPresenter extends Presenter {
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        private final TextView mTitle;
+        private final TextView mSubtitle;
+        private final TextView mBody;
+        private final int mUnderTitleSpacing;
+        private final int mUnderSubtitleSpacing;
+
+        public ViewHolder(View view) {
+            super(view);
+            mTitle = (TextView) view.findViewById(R.id.lb_details_description_title);
+            mSubtitle = (TextView) view.findViewById(R.id.lb_details_description_subtitle);
+            mBody = (TextView) view.findViewById(R.id.lb_details_description_body);
+            int interTextSpacing = view.getContext().getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_overview_description_intertext_spacing);
+            FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
+            mUnderTitleSpacing = interTextSpacing - titleFontMetricsInt.descent;
+            FontMetricsInt subtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
+            mUnderSubtitleSpacing = interTextSpacing - subtitleFontMetricsInt.descent;
+        }
+
+        public TextView getTitle() {
+            return mTitle;
+        }
+
+        public TextView getSubtitle() {
+            return mSubtitle;
+        }
+
+        public TextView getBody() {
+            return mBody;
+        }
+
+        private FontMetricsInt getFontMetricsInt(TextView textView) {
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            paint.setTextSize(textView.getTextSize());
+            paint.setTypeface(textView.getTypeface());
+            return paint.getFontMetricsInt();
+        }
+    }
+
+    @Override
+    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(R.layout.lb_details_description, parent, false);
+        return new ViewHolder(v);
+    }
+
+    @Override
+    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        ViewHolder vh = (ViewHolder) viewHolder;
+        DetailsOverviewRow row = (DetailsOverviewRow) item;
+        onBindDescription(vh, row.getItem());
+
+        boolean hasTitle = true;
+        if (TextUtils.isEmpty(vh.mTitle.getText())) {
+            vh.mTitle.setVisibility(View.GONE);
+            hasTitle = false;
+        } else {
+            vh.mTitle.setVisibility(View.VISIBLE);
+        }
+
+        boolean hasSubtitle = true;
+        if (TextUtils.isEmpty(vh.mSubtitle.getText())) {
+            vh.mSubtitle.setVisibility(View.GONE);
+            hasSubtitle = false;
+        } else {
+            vh.mSubtitle.setVisibility(View.VISIBLE);
+            if (hasTitle) {
+                setTopMargin(vh.mSubtitle, vh.mUnderTitleSpacing);
+            } else {
+                setTopMargin(vh.mSubtitle, 0);
+            }
+        }
+
+        if (TextUtils.isEmpty(vh.mBody.getText())) {
+            vh.mBody.setVisibility(View.GONE);
+        } else {
+            vh.mBody.setVisibility(View.VISIBLE);
+            if (hasSubtitle) {
+                setTopMargin(vh.mBody, vh.mUnderSubtitleSpacing);
+            } else if (hasTitle) {
+                setTopMargin(vh.mBody, vh.mUnderTitleSpacing);
+            } else {
+                setTopMargin(vh.mBody, 0);
+            }
+        }
+    }
+
+    /**
+     * Binds the data from the item referenced in the DetailsOverviewRow to the
+     * ViewHolder.
+     *
+     * @param vh The ViewHolder for this details description view.
+     * @param item The item from the DetailsOverviewRow being presented.
+     */
+    protected abstract void onBindDescription(ViewHolder vh, Object item);
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
+
+    private void setTopMargin(TextView textView, int topMargin) {
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+        lp.topMargin = topMargin;
+        textView.setLayoutParams(lp);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Action.java b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
new file mode 100644
index 0000000..31d0fb5
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
@@ -0,0 +1,124 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * An action that can be shown on a details page. It contains one or two lines
+ * of text and an optional image.
+ */
+public class Action {
+
+    private long mId = NO_ID;
+    private Drawable mIcon;
+    private CharSequence mLabel1;
+    private CharSequence mLabel2;
+
+    public Action(long id) {
+        this(id, "");
+    }
+
+    public Action(long id, CharSequence label) {
+        this(id, label, null);
+    }
+
+    public Action(long id, CharSequence label1, CharSequence label2) {
+        this(id, label1, label2, null);
+    }
+
+    public Action(long id, CharSequence label1, CharSequence label2, Drawable icon) {
+        setId(id);
+        setLabel1(label1);
+        setLabel2(label2);
+        setIcon(icon);
+    }
+
+    /**
+     * Set id for this action.
+     */
+    public final void setId(long id) {
+        mId = id;
+    }
+
+    /**
+     * Returns the id for this action.
+     */
+    public final long getId() {
+        return mId;
+    }
+
+    /**
+     * Set the first line label for this action.
+     */
+    public final void setLabel1(CharSequence label) {
+        mLabel1 = label;
+    }
+
+    /**
+     * Returns the first line label for this action.
+     */
+    public final CharSequence getLabel1() {
+        return mLabel1;
+    }
+
+    /**
+     * Set the second line label for this action.
+     */
+    public final void setLabel2(CharSequence label) {
+        mLabel2 = label;
+    }
+
+    /**
+     * Returns the second line label for this action.
+     */
+    public final CharSequence getLabel2() {
+        return mLabel2;
+    }
+
+    /**
+     * Set the icon drawable for this action.
+     */
+    public final void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    /**
+     * Returns the icon drawable for this action.
+     */
+    public final Drawable getIcon() {
+        return mIcon;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        if (!TextUtils.isEmpty(mLabel1)) {
+            sb.append(mLabel1);
+        }
+        if (!TextUtils.isEmpty(mLabel2)) {
+            if (!TextUtils.isEmpty(mLabel1)) {
+                sb.append(" ");
+            }
+            sb.append(mLabel2);
+        }
+        if (mIcon != null && sb.length() == 0) {
+            sb.append("(action icon)");
+        }
+        return sb.toString();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
new file mode 100644
index 0000000..7698872
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -0,0 +1,146 @@
+/*
+ * 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.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+class ActionPresenterSelector extends PresenterSelector {
+
+    private final Presenter mOneLineActionPresenter = new OneLineActionPresenter();
+    private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter();
+    private OnActionClickedListener mOnActionClickedListener;
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        Action action = (Action) item;
+        if (TextUtils.isEmpty(action.getLabel2())) {
+            return mOneLineActionPresenter;
+        } else {
+            return mTwoLineActionPresenter;
+        }
+    }
+
+    public final void setOnActionClickedListener(OnActionClickedListener listener) {
+        mOnActionClickedListener = listener;
+    }
+
+    public final OnActionClickedListener getOnActionClickedListener() {
+        return mOnActionClickedListener;
+    }
+
+    static class ActionViewHolder extends Presenter.ViewHolder {
+        Action mAction;
+        ImageView mIconView;
+        TextView mLabel;
+
+        public ActionViewHolder(View view) {
+            super(view);
+            mIconView = (ImageView) view.findViewById(R.id.lb_action_icon);
+            mLabel = (TextView) view.findViewById(R.id.lb_action_text);
+        }
+    }
+
+    class OneLineActionPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_1_line, parent, false);
+            final ActionViewHolder vh = new ActionViewHolder(v);
+            v.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (ActionPresenterSelector.this.mOnActionClickedListener != null &&
+                            vh.mAction != null) {
+                            ActionPresenterSelector.this.mOnActionClickedListener.onActionClicked(vh.mAction);
+                        }
+                    }
+            });
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mAction = action;
+            vh.mLabel.setText(action.getLabel1());
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ((ActionViewHolder) viewHolder).mAction = null;
+        }
+    }
+
+    class TwoLineActionPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_2_lines, parent, false);
+            final ActionViewHolder vh = new ActionViewHolder(v);
+            v.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (ActionPresenterSelector.this.mOnActionClickedListener != null &&
+                            vh.mAction != null) {
+                            ActionPresenterSelector.this.mOnActionClickedListener.onActionClicked(vh.mAction);
+                        }
+                    }
+            });
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mAction = action;
+
+            int horizontalPadding = vh.view.getContext().getResources()
+                    .getDimensionPixelSize(R.dimen.lb_action_1_line_padding_left);
+            if (action.getIcon() != null) {
+                vh.view.setPadding(0, 0, horizontalPadding, 0);
+                vh.mIconView.setVisibility(View.VISIBLE);
+                // TODO: scale this?
+                vh.mIconView.setImageDrawable(action.getIcon());
+            } else {
+                vh.view.setPadding(horizontalPadding, 0, horizontalPadding, 0);
+                vh.mIconView.setVisibility(View.GONE);
+            }
+            CharSequence line1 = action.getLabel1();
+            CharSequence line2 = action.getLabel2();
+            if (TextUtils.isEmpty(line1)) {
+                vh.mLabel.setText(line2);
+            } else if (TextUtils.isEmpty(line2)) {
+                vh.mLabel.setText(line1);
+            } else {
+                vh.mLabel.setText(line1 + "\n" + line2);
+            }
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mIconView.setVisibility(View.GONE);
+            vh.view.setPadding(0, 0, 0, 0);
+            vh.mAction = null;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
new file mode 100644
index 0000000..74bb038
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Adapter implemented with an {@link ArrayList}.
+ */
+public class ArrayObjectAdapter extends ObjectAdapter {
+
+    private ArrayList<Object> mItems = new ArrayList<Object>();
+
+    public ArrayObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    public ArrayObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    public ArrayObjectAdapter() {
+        super();
+    }
+
+    @Override
+    public int size() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object get(int index) {
+        return mItems.get(index);
+    }
+
+    /**
+     * Adds an item to the end of the list.
+     *
+     * @param item The item to add to the end of the list.
+     */
+    public void add(Object item) {
+        add(mItems.size(), item);
+    }
+
+    /**
+     * Inserts an item into this list at the specified index.
+     *
+     * @param index The index at which the item should be inserted.
+     * @param item The item to insert into the list.
+     */
+    public void add(int index, Object item) {
+        mItems.add(index, item);
+        notifyItemRangeInserted(index, 1);
+    }
+
+    /**
+     * Adds the objects in the given collection to the list, starting at the
+     * given index.
+     *
+     * @param index The index at which the items should be inserted.
+     * @param items A {@link Collection} of items to insert.
+     */
+    public void addAll(int index, Collection items) {
+        int itemsCount = items.size();
+        mItems.addAll(index, items);
+        notifyItemRangeInserted(index, itemsCount);
+    }
+
+    /**
+     * Removes the first occurrence of the given item from the list.
+     *
+     * @param item The item to remove from the list.
+     * @return True if the item was found and thus removed from the list.
+     */
+    public boolean remove(Object item) {
+        int index = mItems.indexOf(item);
+        if (index >= 0) {
+            mItems.remove(index);
+            notifyItemRangeRemoved(index, 1);
+        }
+        return index >= 0;
+    }
+
+    /**
+     * Removes a range of items from the list. The range is specified by giving
+     * the starting position and the number of elements to remove.
+     *
+     * @param position The index of the first item to remove.
+     * @param count The number of items to remove.
+     * @return The number of items removed.
+     */
+    public int removeItems(int position, int count) {
+        int itemsToRemove = Math.min(count, mItems.size() - position);
+
+        for (int i = 0; i < itemsToRemove; i++) {
+            mItems.remove(position);
+        }
+        notifyItemRangeRemoved(position, itemsToRemove);
+        return itemsToRemove;
+    }
+
+    /**
+     * Removes all items from this list, leaving it empty.
+     */
+    public void clear() {
+        int itemCount = mItems.size();
+        mItems.clear();
+        notifyItemRangeRemoved(0, itemCount);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
new file mode 100644
index 0000000..20360f7
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -0,0 +1,891 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+
+import java.util.ArrayList;
+
+/**
+ * A card style layout that arranges its children in a vertical column according
+ * to the card type set for the parent and the card view type property set for
+ * these children. A BaseCardView can have from 1 to 3 card areas, depending
+ * on the chosen card type. Children are assigned to these areas according to
+ * the view type indicated by their layout parameters. The card type defines
+ * when these card areas are visible or not, and how they animate inside the
+ * parent card. These transitions are triggered when the card changes state. A
+ * card has 2 sets of states: Activated(true/false), and Selected(true/false).
+ * These states, combined with the card type chosen, determine what areas of the
+ * card are visible depending on the current state. They card type also
+ * determines the animations that are triggered when transitioning between
+ * states. The card states are set by calling {@link #setActivated(boolean)
+ * setActivated()} and {@link #setSelected(boolean) setSelected()}.
+ * <p>
+ * See {@link BaseCardView.LayoutParams BaseCardView.LayoutParams} for
+ * layout attributes.
+ * </p>
+ */
+public class BaseCardView extends ViewGroup {
+    private static final String TAG = "BaseCardView";
+    private static final boolean DEBUG = false;
+
+    /**
+     * A simple card type with a single layout area. This card type does not
+     * change its layout or size as it transitions between
+     * Activated/Not-Activated or Selected/Unselected states.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_MAIN_ONLY = 0;
+
+    /**
+     * A Card type with 2 layout areas: A main area, always visible, and an info
+     * area, which is only visible when the card is set to its Active state. The
+     * info area fades in over the main area, and does not cause the card height
+     * to change.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_OVER = 1;
+
+    /**
+     * A Card type with 2 layout areas: A main area, always visible, and an info
+     * area, which is only visible when the card is set to its Active state. The
+     * info area appears below the main area, causing the total card height to
+     * change when the card switches between Active and Inactive states.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_UNDER = 2;
+
+    /**
+     * A Card type with 3 layout areas: A main area, always visible; an info
+     * area, which is only visible when the card is set to its Active state; and
+     * an extra area, which only becomes visible when the card is set to
+     * Selected state. The info area appears below the main area, causing the
+     * total card height to change when the card switches between Active and
+     * Inactive states. The extra area only appears if the card stays in its
+     * Selected state for a certain (small) amount of time. It animates in at
+     * the bottom of the card, shifting up the info view. This does not affect
+     * the card height.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3;
+
+    /**
+     * Indicates that a card region is always visible.
+     */
+    public static final int CARD_REGION_VISIBLE_ALWAYS = 0;
+
+    /**
+     * Indicates that a card region is visible when the card is activated.
+     */
+    public static final int CARD_REGION_VISIBLE_ACTIVATED = 1;
+
+    /**
+     * Indicates that a card region is visible when the card is selected.
+     */
+    public static final int CARD_REGION_VISIBLE_SELECTED = 2;
+
+    private static final int CARD_TYPE_INVALID = 4;
+
+    private int mCardType;
+    private int mInfoVisibility;
+    private int mExtraVisibility;
+
+    private ArrayList<View> mMainViewList;
+    private ArrayList<View> mInfoViewList;
+    private ArrayList<View> mExtraViewList;
+
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+    private boolean mDelaySelectedAnim;
+    private int mSelectedAnimationDelay;
+    private final int mActivatedAnimDuration;
+    private final int mSelectedAnimDuration;
+
+    private float mInfoOffset;
+    private float mInfoVisFraction;
+    private float mInfoAlpha = 1.0f;
+    private Animation mAnim;
+
+    private final Runnable mAnimationTrigger = new Runnable() {
+        @Override
+        public void run() {
+            animateInfoOffset(true);
+        }
+    };
+
+    public BaseCardView(Context context) {
+        this(context, null);
+    }
+
+    public BaseCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.baseCardViewStyle);
+    }
+
+    public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView, defStyle, 0);
+
+        try {
+            mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
+            mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility,
+                    CARD_REGION_VISIBLE_ACTIVATED);
+            mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility,
+                    CARD_REGION_VISIBLE_SELECTED);
+            // Extra region should never show before info region.
+            if (mExtraVisibility < mInfoVisibility) {
+                mExtraVisibility = mInfoVisibility;
+            }
+
+            mSelectedAnimationDelay = a.getInteger(
+                    R.styleable.lbBaseCardView_selectedAnimationDelay,
+                    getResources().getInteger(R.integer.lb_card_selected_animation_delay));
+
+            mSelectedAnimDuration = a.getInteger(
+                    R.styleable.lbBaseCardView_selectedAnimationDuration,
+                    getResources().getInteger(R.integer.lb_card_selected_animation_duration));
+
+            mActivatedAnimDuration =
+                    a.getInteger(R.styleable.lbBaseCardView_activatedAnimationDuration,
+                    getResources().getInteger(R.integer.lb_card_activated_animation_duration));
+        } finally {
+            a.recycle();
+        }
+
+        mDelaySelectedAnim = true;
+
+        mMainViewList = new ArrayList<View>();
+        mInfoViewList = new ArrayList<View>();
+        mExtraViewList = new ArrayList<View>();
+
+        mInfoOffset = 0.0f;
+        mInfoVisFraction = 0.0f;
+    }
+
+    /**
+     * Sets a flag indicating if the Selected animation (if the selected card
+     * type implements one) should run immediately after the card is selected,
+     * or if it should be delayed. The default behavior is to delay this
+     * animation. This is a one-shot override. If set to false, after the card
+     * is selected and the selected animation is triggered, this flag is
+     * automatically reset to true. This is useful when you want to change the
+     * default behavior, and have the selected animation run immediately. One
+     * such case could be when focus moves from one row to the other, when
+     * instead of delaying the selected animation until the user pauses on a
+     * card, it may be desirable to trigger the animation for that card
+     * immediately.
+     *
+     * @param delay True (default) if the selected animation should be delayed
+     *            after the card is selected, or false if the animation should
+     *            run immediately the next time the card is Selected.
+     */
+    public void setSelectedAnimationDelayed(boolean delay) {
+        mDelaySelectedAnim = delay;
+    }
+
+    /**
+     * Returns a boolean indicating if the selected animation will run
+     * immediately or be delayed the next time the card is Selected.
+     *
+     * @return true if this card is set to delay the selected animation the next
+     *         time it is selected, or false if the selected animation will run
+     *         immediately the next time the card is selected.
+     */
+    public boolean isSelectedAnimationDelayed() {
+        return mDelaySelectedAnim;
+    }
+
+    /**
+     * Sets the type of this Card.
+     *
+     * @param type The desired card type.
+     */
+    public void setCardType(int type) {
+        if (mCardType != type) {
+            if (type >= CARD_TYPE_MAIN_ONLY && type < CARD_TYPE_INVALID) {
+                // Valid card type
+                mCardType = type;
+            } else {
+                Log.e(TAG, "Invalid card type specified: " + type +
+                        ". Defaulting to type CARD_TYPE_MAIN_ONLY.");
+                mCardType = CARD_TYPE_MAIN_ONLY;
+            }
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the type of this Card.
+     *
+     * @return The type of this card.
+     */
+    public int getCardType() {
+        return mCardType;
+    }
+
+    public void setInfoVisibility(int visibility) {
+        if (mInfoVisibility != visibility) {
+            mInfoVisibility = visibility;
+            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED && isSelected()) {
+                mInfoVisFraction = 1.0f;
+            } else {
+                mInfoVisFraction = 0.0f;
+            }
+            requestLayout();
+        }
+    }
+
+    public int getInfoVisibility() {
+        return mInfoVisibility;
+    }
+
+    public void setExtraVisibility(int visibility) {
+        if (mExtraVisibility != visibility) {
+            mExtraVisibility = visibility;
+            requestLayout();
+        }
+    }
+
+    public int getExtraVisibility() {
+        return mExtraVisibility;
+    }
+
+    /**
+     * Sets the Activated state of this Card. This can trigger changes in the
+     * card layout, resulting in views to become visible or hidden. A card is
+     * normally set to Activated state when its parent container (like a Row)
+     * receives focus, and then activates all of its children.
+     *
+     * @param activated True if the card is ACTIVE, or false if INACTIVE.
+     * @see #isActivated()
+     */
+    @Override
+    public void setActivated(boolean activated) {
+        if (activated != isActivated()) {
+            super.setActivated(activated);
+            applyActiveState(isActivated());
+        }
+    }
+
+    /**
+     * Sets the Selected state of this Card. This can trigger changes in the
+     * card layout, resulting in views to become visible or hidden. A card is
+     * normally set to Selected state when it receives input focus.
+     *
+     * @param selected True if the card is Selected, or false otherwise.
+     * @see #isSelected()
+     */
+    @Override
+    public void setSelected(boolean selected) {
+        if (selected != isSelected()) {
+            super.setSelected(selected);
+            applySelectedState(isSelected());
+        }
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mMeasuredWidth = 0;
+        mMeasuredHeight = 0;
+        int state = 0;
+        int mainHeight = 0;
+        int infoHeight = 0;
+        int extraHeight = 0;
+
+        findChildrenViews();
+
+        final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        // MAIN is always present
+        for (int i = 0; i < mMainViewList.size(); i++) {
+            View mainView = mMainViewList.get(i);
+            if (mainView.getVisibility() != View.GONE) {
+                measureChild(mainView, unspecifiedSpec, unspecifiedSpec);
+                mMeasuredWidth = Math.max(mMeasuredWidth, mainView.getMeasuredWidth());
+                mainHeight += mainView.getMeasuredHeight();
+                state = View.combineMeasuredStates(state, mainView.getMeasuredState());
+            }
+        }
+
+
+        // The MAIN area determines the card width
+        int cardWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+
+        if (hasInfoRegion()) {
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                View infoView = mInfoViewList.get(i);
+                if (infoView.getVisibility() != View.GONE) {
+                    measureChild(infoView, cardWidthMeasureSpec, unspecifiedSpec);
+                    if (mCardType != CARD_TYPE_INFO_OVER) {
+                        infoHeight += infoView.getMeasuredHeight();
+                    }
+                    state = View.combineMeasuredStates(state, infoView.getMeasuredState());
+                }
+            }
+
+            if (hasExtraRegion()) {
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    View extraView = mExtraViewList.get(i);
+                    if (extraView.getVisibility() != View.GONE) {
+                        measureChild(extraView, cardWidthMeasureSpec, unspecifiedSpec);
+                        extraHeight += extraView.getMeasuredHeight();
+                        state = View.combineMeasuredStates(state, extraView.getMeasuredState());
+                    }
+                }
+            }
+        }
+
+        boolean infoAnimating = hasInfoRegion() && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED;
+        mMeasuredHeight = (int) (mainHeight +
+                (infoAnimating ? (infoHeight * mInfoVisFraction) : infoHeight)
+                + extraHeight - (infoAnimating ? 0 : mInfoOffset));
+
+        // Report our final dimensions.
+        setMeasuredDimension(View.resolveSizeAndState(mMeasuredWidth + getPaddingLeft() +
+                getPaddingRight(), widthMeasureSpec, state),
+                View.resolveSizeAndState(mMeasuredHeight + getPaddingTop() + getPaddingBottom(),
+                        heightMeasureSpec, state << View.MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        float currBottom = getPaddingTop();
+
+        // MAIN is always present
+        for (int i = 0; i < mMainViewList.size(); i++) {
+            View mainView = mMainViewList.get(i);
+            if (mainView.getVisibility() != View.GONE) {
+                mainView.layout(getPaddingLeft(),
+                        (int) currBottom,
+                                mMeasuredWidth + getPaddingLeft(),
+                        (int) (currBottom + mainView.getMeasuredHeight()));
+                currBottom += mainView.getMeasuredHeight();
+            }
+        }
+
+        if (hasInfoRegion()) {
+            float infoHeight = 0f;
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                infoHeight += mInfoViewList.get(i).getMeasuredHeight();
+            }
+
+            if (mCardType == CARD_TYPE_INFO_OVER) {
+                // retract currBottom to overlap the info views on top of main
+                currBottom -= infoHeight;
+                if (currBottom < 0) {
+                    currBottom = 0;
+                }
+            } else if (mCardType == CARD_TYPE_INFO_UNDER) {
+                if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+                    infoHeight = infoHeight * mInfoVisFraction;
+                }
+            } else {
+                currBottom -= mInfoOffset;
+            }
+
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                View infoView = mInfoViewList.get(i);
+                if (infoView.getVisibility() != View.GONE) {
+                    int viewHeight = infoView.getMeasuredHeight();
+                    if (viewHeight > infoHeight) {
+                        viewHeight = (int) infoHeight;
+                    }
+                    infoView.layout(getPaddingLeft(),
+                            (int) currBottom,
+                                    mMeasuredWidth + getPaddingLeft(),
+                            (int) (currBottom + viewHeight));
+                    currBottom += viewHeight;
+                    infoHeight -= viewHeight;
+                    if (infoHeight <= 0) {
+                        break;
+                    }
+                }
+            }
+
+            if (hasExtraRegion()) {
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    View extraView = mExtraViewList.get(i);
+                    if (extraView.getVisibility() != View.GONE) {
+                        extraView.layout(getPaddingLeft(),
+                                (int) currBottom,
+                                        mMeasuredWidth + getPaddingLeft(),
+                                (int) (currBottom + extraView.getMeasuredHeight()));
+                        currBottom += extraView.getMeasuredHeight();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        removeCallbacks(mAnimationTrigger);
+        cancelAnimations();
+        mInfoOffset = 0.0f;
+        mInfoVisFraction = 0.0f;
+    }
+
+    private boolean hasInfoRegion() {
+        return mCardType != CARD_TYPE_MAIN_ONLY;
+    }
+
+    private boolean hasExtraRegion() {
+        return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA;
+    }
+
+    private boolean isRegionVisible(int regionVisibility) {
+        switch (regionVisibility) {
+            case CARD_REGION_VISIBLE_ALWAYS:
+                return true;
+            case CARD_REGION_VISIBLE_ACTIVATED:
+                return isActivated();
+            case CARD_REGION_VISIBLE_SELECTED:
+                return isActivated() && isSelected();
+            default:
+                if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility);
+                return false;
+        }
+    }
+
+    private void findChildrenViews() {
+        mMainViewList.clear();
+        mInfoViewList.clear();
+        mExtraViewList.clear();
+
+        final int count = getChildCount();
+
+        boolean infoVisible = isRegionVisible(mInfoVisibility);
+        boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f;
+
+        if (mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+            infoVisible = infoVisible && mInfoVisFraction > 0f;
+        }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+
+            if (child == null) {
+                continue;
+            }
+
+            BaseCardView.LayoutParams lp = (BaseCardView.LayoutParams) child
+                    .getLayoutParams();
+            if (lp.viewType == LayoutParams.VIEW_TYPE_INFO) {
+                mInfoViewList.add(child);
+                child.setVisibility(infoVisible ? View.VISIBLE : View.GONE);
+            } else if (lp.viewType == LayoutParams.VIEW_TYPE_EXTRA) {
+                mExtraViewList.add(child);
+                child.setVisibility(extraVisible ? View.VISIBLE : View.GONE);
+            } else {
+                // Default to MAIN
+                mMainViewList.add(child);
+                child.setVisibility(View.VISIBLE);
+            }
+        }
+
+    }
+
+    private void applyActiveState(boolean active) {
+        if (hasInfoRegion() && mInfoVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
+            setInfoViewVisibility(active);
+        }
+        if (hasExtraRegion() && mExtraVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
+            //setExtraVisibility(active);
+        }
+    }
+
+    private void setInfoViewVisibility(boolean visible) {
+        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
+            // Active state changes for card type
+            // CARD_TYPE_INFO_UNDER_WITH_EXTRA
+            if (visible) {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(View.VISIBLE);
+                }
+            } else {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(View.GONE);
+                }
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    mExtraViewList.get(i).setVisibility(View.GONE);
+                }
+                mInfoOffset = 0.0f;
+            }
+        } else if (mCardType == CARD_TYPE_INFO_UNDER) {
+            // Active state changes for card type CARD_TYPE_INFO_UNDER
+            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+                animateInfoHeight(visible);
+            } else {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+                }
+            }
+        } else if (mCardType == CARD_TYPE_INFO_OVER) {
+            // Active state changes for card type CARD_TYPE_INFO_OVER
+            animateInfoAlpha(visible);
+        }
+    }
+
+    private void applySelectedState(boolean focused) {
+        removeCallbacks(mAnimationTrigger);
+
+        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
+            // Focus changes for card type CARD_TYPE_INFO_UNDER_WITH_EXTRA
+            if (focused) {
+                if (!mDelaySelectedAnim) {
+                    post(mAnimationTrigger);
+                    mDelaySelectedAnim = true;
+                } else {
+                    postDelayed(mAnimationTrigger, mSelectedAnimationDelay);
+                }
+            } else {
+                animateInfoOffset(false);
+            }
+        } else if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+            setInfoViewVisibility(focused);
+        }
+    }
+
+    private void cancelAnimations() {
+        if (mAnim != null) {
+            mAnim.cancel();
+            mAnim = null;
+        }
+    }
+
+    // This animation changes the Y offset of the info and extra views,
+    // so that they animate UP to make the extra info area visible when a
+    // card is selected.
+    private void animateInfoOffset(boolean shown) {
+        cancelAnimations();
+
+        int extraHeight = 0;
+        if (shown) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+            for (int i = 0; i < mExtraViewList.size(); i++) {
+                View extraView = mExtraViewList.get(i);
+                extraView.setVisibility(View.VISIBLE);
+                extraView.measure(widthSpec, heightSpec);
+                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
+            }
+        }
+
+        mAnim = new InfoOffsetAnimation(mInfoOffset, shown ? extraHeight : 0);
+        mAnim.setDuration(mSelectedAnimDuration);
+        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoOffset == 0f) {
+                    for (int i = 0; i < mExtraViewList.size(); i++) {
+                        mExtraViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+                @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    // This animation changes the visible height of the info views,
+    // so that they animate in and out of view.
+    private void animateInfoHeight(boolean shown) {
+        cancelAnimations();
+
+        int extraHeight = 0;
+        if (shown) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+            for (int i = 0; i < mExtraViewList.size(); i++) {
+                View extraView = mExtraViewList.get(i);
+                extraView.setVisibility(View.VISIBLE);
+                extraView.measure(widthSpec, heightSpec);
+                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
+            }
+        }
+
+        mAnim = new InfoHeightAnimation(mInfoVisFraction, shown ? 1.0f : 0f);
+        mAnim.setDuration(mSelectedAnimDuration);
+        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+                @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoOffset == 0f) {
+                    for (int i = 0; i < mExtraViewList.size(); i++) {
+                        mExtraViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    // This animation changes the alpha of the info views, so they animate in
+    // and out. It's meant to be used when the info views are overlaid on top of
+    // the main view area. It gets triggered by a change in the Active state of
+    // the card.
+    private void animateInfoAlpha(boolean shown) {
+        cancelAnimations();
+
+        if (shown) {
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                mInfoViewList.get(i).setVisibility(View.VISIBLE);
+            }
+        }
+
+        mAnim = new InfoAlphaAnimation(mInfoAlpha, shown ? 1.0f : 0.0f);
+        mAnim.setDuration(mActivatedAnimDuration);
+        mAnim.setInterpolator(new DecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoAlpha == 0.0) {
+                    for (int i = 0; i < mInfoViewList.size(); i++) {
+                        mInfoViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new BaseCardView.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new BaseCardView.LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof BaseCardView.LayoutParams;
+    }
+
+    /**
+     * Per-child layout information associated with BaseCardView.
+     */
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        public static final int VIEW_TYPE_MAIN = 0;
+        public static final int VIEW_TYPE_INFO = 1;
+        public static final int VIEW_TYPE_EXTRA = 2;
+
+        /**
+         * Card component type for the view associated with these LayoutParams.
+         */
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
+                @ViewDebug.IntToString(from = VIEW_TYPE_MAIN, to = "MAIN"),
+                @ViewDebug.IntToString(from = VIEW_TYPE_INFO, to = "INFO"),
+                @ViewDebug.IntToString(from = VIEW_TYPE_EXTRA, to = "EXTRA")
+        })
+        public int viewType = VIEW_TYPE_MAIN;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);
+
+            viewType = a.getInt(
+                    R.styleable.lbBaseCardView_Layout_layout_viewType, VIEW_TYPE_MAIN);
+
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * Copy constructor. Clones the width, height, and View Type of the
+         * source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            this.viewType = source.viewType;
+        }
+    }
+
+    // Helper animation class used in the animation of the info and extra
+    // fields vertically within the card
+    private class InfoOffsetAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoOffsetAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoOffset = mStartValue + (interpolatedTime * mDelta);
+            requestLayout();
+        }
+    }
+
+    // Helper animation class used in the animation of the visible height
+    // for the info fields.
+    private class InfoHeightAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoHeightAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoVisFraction = mStartValue + (interpolatedTime * mDelta);
+            requestLayout();
+        }
+    }
+
+    // Helper animation class used to animate the alpha for the info views
+    // when they are fading in or out of view.
+    private class InfoAlphaAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoAlphaAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoAlpha = mStartValue + (interpolatedTime * mDelta);
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                mInfoViewList.get(i).setAlpha(mInfoAlpha);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(this.getClass().getSimpleName()).append(" : ");
+            sb.append("cardType=");
+            switch(mCardType) {
+                case CARD_TYPE_MAIN_ONLY:
+                    sb.append("MAIN_ONLY");
+                    break;
+                case CARD_TYPE_INFO_OVER:
+                    sb.append("INFO_OVER");
+                    break;
+                case CARD_TYPE_INFO_UNDER:
+                    sb.append("INFO_UNDER");
+                    break;
+                case CARD_TYPE_INFO_UNDER_WITH_EXTRA:
+                    sb.append("INFO_UNDER_WITH_EXTRA");
+                    break;
+                default:
+                    sb.append("INVALID");
+                    break;
+            }
+            sb.append(" : ");
+            sb.append(mMainViewList.size()).append(" main views, ");
+            sb.append(mInfoViewList.size()).append(" info views, ");
+            sb.append(mExtraViewList.size()).append(" extra views : ");
+            sb.append("infoVisibility=").append(mInfoVisibility).append(" ");
+            sb.append("extraVisibility=").append(mExtraVisibility).append(" ");
+            sb.append("isActivated=").append(isActivated());
+            sb.append(" : ");
+            sb.append("isSelected=").append(isSelected());
+            return sb.toString();
+        } else {
+            return super.toString();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
new file mode 100644
index 0000000..854f5de
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -0,0 +1,470 @@
+/*
+ * 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.Rect;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+
+/**
+ * Base class for vertically and horizontally scrolling lists. The items come
+ * from the {@link RecyclerView.Adapter} associated with this view.
+ * @hide
+ */
+abstract class BaseGridView extends RecyclerView {
+
+    /**
+     * Always keep focused item at a aligned position.  Developer can use
+     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
+     * In this mode, the last focused position will be remembered and restored when focus
+     * is back to the view.
+     */
+    public final static int FOCUS_SCROLL_ALIGNED = 0;
+
+    /**
+     * Scroll to make the focused item inside client area.
+     */
+    public final static int FOCUS_SCROLL_ITEM = 1;
+
+    /**
+     * Scroll a page of items when focusing to item outside the client area.
+     * The page size matches the client area size of RecyclerView.
+     */
+    public final static int FOCUS_SCROLL_PAGE = 2;
+
+    /**
+     * The first item is aligned with the low edge of the viewport. When
+     * navigating away from the first item, the focus maintains a middle
+     * location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
+
+    /**
+     * The last item is aligned with the high edge of the viewport when
+     * navigating to the end of list. When navigating away from the end, the
+     * focus maintains a middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
+
+    /**
+     * The first item and last item are aligned with the two edges of the
+     * viewport. When navigating in the middle of list, the focus maintains a
+     * middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_BOTH_EDGE =
+            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
+
+    /**
+     * The focused item always stays in a middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_NO_EDGE = 0;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1;
+
+    protected final GridLayoutManager mLayoutManager;
+
+    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager = new GridLayoutManager(this);
+        setLayoutManager(mLayoutManager);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setHasFixedSize(true);
+        setChildrenDrawingOrderEnabled(true);
+    }
+
+    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
+        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
+        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
+        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
+        mLayoutManager.setVerticalMargin(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
+        mLayoutManager.setHorizontalMargin(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
+        a.recycle();
+    }
+
+    /**
+     * Set the strategy used to scroll in response to item focus changing:
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     */
+    public void setFocusScrollStrategy(int scrollStrategy) {
+        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
+            && scrollStrategy != FOCUS_SCROLL_PAGE) {
+            throw new IllegalArgumentException("Invalid scrollStrategy");
+        }
+        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
+        requestLayout();
+    }
+
+    /**
+     * Returns the strategy used to scroll in response to item focus changing.
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     */
+    public int getFocusScrollStrategy() {
+        return mLayoutManager.getFocusScrollStrategy();
+    }
+
+    /**
+     * Set how the focused item gets aligned in the view.
+     *
+     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
+     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
+     *        {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public void setWindowAlignment(int windowAlignment) {
+        mLayoutManager.setWindowAlignment(windowAlignment);
+        requestLayout();
+    }
+
+    /**
+     * Get how the focused item gets aligned in the view.
+     *
+     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
+     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public int getWindowAlignment() {
+        return mLayoutManager.getWindowAlignment();
+    }
+
+    /**
+     * Set the absolute offset in pixels for window alignment.
+     *
+     * @param offset The number of pixels to offset. Can be negative for
+     *        alignment from the high edge, or positive for alignment from the
+     *        low edge.
+     */
+    public void setWindowAlignmentOffset(int offset) {
+        mLayoutManager.setWindowAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Get the absolute offset in pixels for window alignment.
+     *
+     * @return The number of pixels to offset. Will be negative for alignment
+     *         from the high edge, or positive for alignment from the low edge.
+     *         Default value is 0.
+     */
+    public int getWindowAlignmentOffset() {
+        return mLayoutManager.getWindowAlignmentOffset();
+    }
+
+    /**
+     * Set offset percent for window alignment in addition to {@link
+     * #getWindowAlignmentOffset()}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from low edge. Use
+     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     */
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Get offset percent for window alignment in addition to
+     * {@link #getWindowAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the 
+     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getWindowAlignmentOffsetPercent() {
+        return mLayoutManager.getWindowAlignmentOffsetPercent();
+    }
+
+    /**
+     * Set the absolute offset in pixels for item alignment.
+     *
+     * @param offset The number of pixels to offset. Can be negative for
+     *        alignment from the high edge, or positive for alignment from the
+     *        low edge.
+     */
+    public void setItemAlignmentOffset(int offset) {
+        mLayoutManager.setItemAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Get the absolute offset in pixels for item alignment.
+     *
+     * @return The number of pixels to offset. Will be negative for alignment
+     *         from the high edge, or positive for alignment from the low edge.
+     *         Default value is 0.
+     */
+    public int getItemAlignmentOffset() {
+        return mLayoutManager.getItemAlignmentOffset();
+    }
+
+    /**
+     * Set offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from the low edge. Use
+     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     */
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Get offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the
+     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getItemAlignmentOffsetPercent() {
+        return mLayoutManager.getItemAlignmentOffsetPercent();
+    }
+
+    /**
+     * Set the id of the view to align with. Use zero (default) for the item
+     * view itself.
+     */
+    public void setItemAlignmentViewId(int viewId) {
+        mLayoutManager.setItemAlignmentViewId(viewId);
+    }
+
+    /**
+     * Get the id of the view to align with, or zero for the item view itself.
+     */
+    public int getItemAlignmentViewId() {
+        return mLayoutManager.getItemAlignmentViewId();
+    }
+
+    /**
+     * Set the margin in pixels between two child items.
+     */
+    public void setItemMargin(int margin) {
+        mLayoutManager.setItemMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Set the margin in pixels between two child items vertically.
+     */
+    public void setVerticalMargin(int margin) {
+        mLayoutManager.setVerticalMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Get the margin in pixels between two child items vertically.
+     */
+    public int getVerticalMargin() {
+        return mLayoutManager.getVerticalMargin();
+    }
+
+    /**
+     * Set the margin in pixels between two child items horizontally.
+     */
+    public void setHorizontalMargin(int margin) {
+        mLayoutManager.setHorizontalMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Get the margin in pixels between two child items horizontally.
+     */
+    public int getHorizontalMargin() {
+        return mLayoutManager.getHorizontalMargin();
+    }
+
+    /**
+     * Register a callback to be invoked when an item in BaseGridView has
+     * been selected.
+     */
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mLayoutManager.setOnChildSelectedListener(listener);
+    }
+
+    /**
+     * Change the selected item immediately without animation.
+     */
+    public void setSelectedPosition(int position) {
+        mLayoutManager.setSelection(this, position);
+    }
+
+    /**
+     * Change the selected item and run an animation to scroll to the target
+     * position.
+     */
+    public void setSelectedPositionSmooth(int position) {
+        mLayoutManager.setSelectionSmooth(this, position);
+    }
+
+    /**
+     * Get the selected item position.
+     */
+    public int getSelectedPosition() {
+        return mLayoutManager.getSelection();
+    }
+
+    /**
+     * Set if an animation should run when a child changes size or when adding
+     * or removing a child.
+     * <p><i>Unstable API, might change later.</i>
+     */
+    public void setAnimateChildLayout(boolean animateChildLayout) {
+        mLayoutManager.setAnimateChildLayout(animateChildLayout);
+    }
+
+    /**
+     * Return true if an animation will run when a child changes size or when
+     * adding or removing a child.
+     * <p><i>Unstable API, might change later.</i>
+     */
+    public boolean isChildLayoutAnimated() {
+        return mLayoutManager.isChildLayoutAnimated();
+    }
+
+    /**
+     * 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.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     */
+    public void setGravity(int gravity) {
+        mLayoutManager.setGravity(gravity);
+        requestLayout();
+    }
+
+    @Override
+    public void setDescendantFocusability (int focusability) {
+        // enforce FOCUS_AFTER_DESCENDANTS
+        super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+    }
+
+    @Override
+    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
+                previouslyFocusedRect);
+    }
+
+    /**
+     * Get the x/y offsets to final position from current position if the view
+     * is selected.
+     *
+     * @param view The view to get offsets.
+     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
+     *        Y.
+     */
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        mLayoutManager.getViewSelectedOffsets(view, offsets);
+    }
+
+    @Override
+    public int getChildDrawingOrder(int childCount, int i) {
+        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
+    }
+
+    final boolean isChildrenDrawingOrderEnabledInternal() {
+        return isChildrenDrawingOrderEnabled();
+    }
+
+    /**
+     * Disable or enable focus search.
+     */
+    public final void setFocusSearchDisabled(boolean disabled) {
+        mLayoutManager.setFocusSearchDisabled(disabled);
+    }
+
+    /**
+     * Return true if focus search is disabled.
+     */
+    public final boolean isFocusSearchDisabled() {
+        return mLayoutManager.isFocusSearchDisabled();
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
new file mode 100644
index 0000000..64d2270
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
@@ -0,0 +1,42 @@
+/*
+ * 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 java.util.HashMap;
+
+/**
+ * A ClassPresenterSelector selects a {@link Presenter} based on the item's
+ * Java class.
+ */
+public final class ClassPresenterSelector extends PresenterSelector {
+
+    private final HashMap<Class<?>, Presenter> mClassMap = new HashMap<Class<?>, Presenter>();
+
+    public void addClassPresenter(Class<?> cls, Presenter presenter) {
+        mClassMap.put(cls, presenter);
+    }
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        Class<?> cls = item.getClass();
+        Presenter presenter = null;
+
+        do {
+            presenter = mClassMap.get(cls);
+            cls = cls.getSuperclass();
+        } while (presenter == null && cls != null);
+
+        return presenter;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
new file mode 100644
index 0000000..1968822
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.database.Cursor;
+import android.support.v17.leanback.database.CursorMapper;
+import android.util.LruCache;
+
+/**
+ * Adapter implemented with a {@link Cursor}.
+ */
+public class CursorObjectAdapter extends ObjectAdapter {
+    private static final int CACHE_SIZE = 100;
+    private Cursor mCursor;
+    private CursorMapper mMapper;
+    private final LruCache<Integer, Object> mItemCache = new LruCache<Integer, Object>(CACHE_SIZE);
+
+    public CursorObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    public CursorObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    public CursorObjectAdapter() {
+        super();
+    }
+
+    /**
+     * Change the underlying cursor to a new cursor. If there is
+     * an existing cursor it will be closed.
+     *
+     * @param cursor The new cursor to be used.
+     */
+    public void changeCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return;
+        }
+        if (mCursor != null) {
+            mCursor.close();
+        }
+        mCursor = cursor;
+        mItemCache.trimToSize(0);
+        onCursorChanged();
+    }
+
+    /**
+     * Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor),
+     * the returned old Cursor is not closed.
+     *
+     * @param cursor The new cursor to be used.
+     */
+    public Cursor swapCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return mCursor;
+        }
+        Cursor oldCursor = mCursor;
+        mCursor = cursor;
+        mItemCache.trimToSize(0);
+        onCursorChanged();
+        return oldCursor;
+    }
+
+    /**
+     * Called whenever the cursor changes.
+     */
+    protected void onCursorChanged() {
+        notifyChanged();
+    }
+
+    /**
+     * Gets the {@link Cursor} backing the adapter.
+     */
+     public final Cursor getCursor() {
+        return mCursor;
+    }
+
+    /**
+     * Sets the {@link CursorMapper} used to convert {@link Cursor} rows into
+     * Objects.
+     */
+    public final void setMapper(CursorMapper mapper) {
+        boolean changed = mMapper != mapper;
+        mMapper = mapper;
+
+        if (changed) {
+            onMapperChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setMapper(CursorMapper)} is called and a different
+     * mapper is provided.
+     */
+    protected void onMapperChanged() {
+    }
+
+    /**
+     * Gets the {@link CursorMapper} used to convert {@link Cursor} rows into
+     * Objects.
+     */
+    public final CursorMapper getMapper() {
+        return mMapper;
+    }
+
+    @Override
+    public int size() {
+        if (mCursor == null) {
+            return 0;
+        }
+        return mCursor.getCount();
+    }
+
+    @Override
+    public Object get(int index) {
+        if (mCursor == null) {
+            return null;
+        }
+        if (!mCursor.moveToPosition(index)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        Object item = mItemCache.get(index);
+        if (item != null) {
+            return item;
+        }
+        item = mMapper.convert(mCursor);
+        mItemCache.put(index, item);
+        return item;
+    }
+
+    /**
+     * Closes this adapter, closing the backing {@link Cursor} as well.
+     */
+    public void close() {
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
+    }
+
+    /**
+     * Checks whether the adapter, and hence the backing {@link Cursor}, is closed.
+     */
+    public boolean isClosed() {
+        return mCursor == null || mCursor.isClosed();
+    }
+
+    /**
+     * Remove an item from the cache. This will force the item to be re-read
+     * from the data source the next time (@link #get(int)} is called.
+     */
+    protected final void invalidateCache(int index) {
+        mItemCache.remove(index);
+    }
+
+    /**
+     * Remove {@code count} items starting at {@code index}.
+     */
+    protected final void invalidateCache(int index, int count) {
+        for (int limit = count + index; index < limit; index++) {
+            invalidateCache(index);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
new file mode 100644
index 0000000..e5dbfaf
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
@@ -0,0 +1,126 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The overview row for a details fragment. This row consists of an image, a
+ * description view, and optionally a series of actions that can be taken for
+ * the item.
+ */
+public class DetailsOverviewRow extends Row {
+
+    private Object mItem;
+    private Drawable mImageDrawable;
+    private ArrayList<Action> mActions = new ArrayList<Action>();
+
+    /**
+     * Constructor.
+     *
+     * @param item The main item for the details page.
+     */
+    public DetailsOverviewRow(Object item) {
+        super(null);
+        mItem = item;
+        verify();
+    }
+
+    /**
+     * Gets the main item for the details page.
+     */
+    public final Object getItem() {
+        return mItem;
+    }
+
+    /**
+     * Sets a drawable as the image of this details overview.
+     *
+     * @param drawable The drawable to set.
+     */
+    public final void setImageDrawable(Drawable drawable) {
+        mImageDrawable = drawable;
+    }
+
+    /**
+     * Sets a Bitmap as the image of this details overview.
+     *
+     * @param context The context to retrieve display metrics from.
+     * @param bm The bitmap to set.
+     */
+    public final void setImageBitmap(Context context, Bitmap bm) {
+        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
+    }
+
+    /**
+     * Gets the image drawable of this details overview.
+     *
+     * @return The overview's image drawable, or null if no drawable has been
+     *         assigned.
+     */
+    public final Drawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
+    /**
+     * Add an action to the overview.
+     *
+     * @param action The action to add.
+     */
+    public final void addAction(Action action) {
+        mActions.add(action);
+    }
+
+    /**
+     * Add an action to the overview at the specified position.
+     *
+     * @param pos The position to insert the action.
+     * @param action The action to add.
+     */
+    public final void addAction(int pos, Action action) {
+        mActions.add(pos, action);
+    }
+
+    /**
+     * Remove the given action from the overview.
+     *
+     * @param action The action to remove.
+     * @return true if the overview contained the specified action.
+     */
+    public final boolean removeAction(Action action) {
+        return mActions.remove(action);
+    }
+
+    /**
+     * Gets a read-only view of the list of actions of this details overview.
+     *
+     * @return An unmodifiable view of the list of actions.
+     */
+    public final List<Action> getActions() {
+        return Collections.unmodifiableList(mActions);
+    }
+
+    private void verify() {
+        if (mItem == null) {
+            throw new IllegalArgumentException("Object cannot be null");
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
new file mode 100644
index 0000000..3504581
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.Collection;
+
+/**
+ * DetailsOverviewRowPresenter renders {@link DetailsOverviewRow} to display an
+ * overview of an item. Typically this row will be the first row in a fragment
+ * such as {@link android.support.v17.leanback.app.DetailsFragment
+ * DetailsFragment}.
+ *
+ * <p>The detailed description is rendered using a {@link Presenter}.
+ */
+public class DetailsOverviewRowPresenter extends RowPresenter {
+
+    private static final String TAG = "DetailsOverviewRowPresenter";
+    private static final boolean DEBUG = false;
+
+    public static class ViewHolder extends RowPresenter.ViewHolder {
+        final ImageView mImageView;
+        final FrameLayout mDetailsDescriptionFrame;
+        final HorizontalGridView mActionsRow;
+        Presenter.ViewHolder mDetailsDescriptionViewHolder;
+
+        public ViewHolder(View rootView) {
+            super(rootView);
+            mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image);
+            mDetailsDescriptionFrame =
+                    (FrameLayout) rootView.findViewById(R.id.details_overview_description);
+            mActionsRow =
+                    (HorizontalGridView) rootView.findViewById(R.id.details_overview_actions);
+        }
+    }
+
+    private final Presenter mDetailsPresenter;
+    private final ActionPresenterSelector mActionPresenterSelector;
+    private final ItemBridgeAdapter mActionBridgeAdapter;
+
+    /**
+     * Constructor that uses the given {@link Presenter} to render the detailed
+     * description for the row.
+     */
+    public DetailsOverviewRowPresenter(Presenter detailsPresenter) {
+        setSelectEffectEnabled(false);
+        mDetailsPresenter = detailsPresenter;
+        mActionPresenterSelector = new ActionPresenterSelector();
+        mActionBridgeAdapter = new ItemBridgeAdapter();
+        FocusHighlightHelper.setupActionItemFocusHighlight(mActionBridgeAdapter);
+    }
+
+    /**
+     * Set the listener for action click events.
+     */
+    public void setOnActionClickedListener(OnActionClickedListener listener) {
+        mActionPresenterSelector.setOnActionClickedListener(listener);
+    }
+
+    /**
+     * Get the listener for action click events.
+     */
+    public OnActionClickedListener getOnActionClickedListener() {
+        return mActionPresenterSelector.getOnActionClickedListener();
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(R.layout.lb_details_overview, parent, false);
+        ViewHolder vh = new ViewHolder(v);
+        vh.mDetailsDescriptionViewHolder =
+            mDetailsPresenter.onCreateViewHolder(vh.mDetailsDescriptionFrame);
+        vh.mDetailsDescriptionFrame.addView(vh.mDetailsDescriptionViewHolder.view);
+
+        return vh;
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
+
+        DetailsOverviewRow row = (DetailsOverviewRow) item;
+        ViewHolder vh = (ViewHolder) holder;
+        if (row.getImageDrawable() != null) {
+            vh.mImageView.setImageDrawable(row.getImageDrawable());
+        }
+        if (vh.mDetailsDescriptionViewHolder == null) {
+        }
+        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row);
+
+        mActionBridgeAdapter.clear();
+        ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
+        aoa.addAll(0, (Collection)row.getActions());
+        mActionBridgeAdapter.setAdapter(aoa);
+        vh.mActionsRow.setAdapter(mActionBridgeAdapter);
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        super.onUnbindRowViewHolder(holder);
+
+        ViewHolder vh = (ViewHolder) holder;
+        if (vh.mDetailsDescriptionViewHolder != null) {
+            mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
+        }
+
+        vh.mActionsRow.setAdapter(null);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
new file mode 100644
index 0000000..2171992
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
@@ -0,0 +1,51 @@
+/*
+ * 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.view.View;
+
+/**
+ * Interface for highlighting the item that has focus.
+ *
+ */
+public interface FocusHighlight {
+    /**
+     * No zoom factor.
+     */
+    public static final int ZOOM_FACTOR_NONE = 0;
+
+    /**
+     * A small zoom factor, recommended for large item views.
+     */
+    public static final int ZOOM_FACTOR_SMALL = 1;
+
+    /**
+     * A medium zoom factor, recommended for medium sized item views.
+     */
+    public static final int ZOOM_FACTOR_MEDIUM = 2;
+
+    /**
+     * A large zoom factor, recommended for small item views.
+     */
+    public static final int ZOOM_FACTOR_LARGE = 3;
+
+    /**
+     * Called when an item gains or loses focus.
+     * @hide
+     *
+     * @param view The view whose focus is changing.
+     * @param hasFocus True if focus is gained; false otherwise.
+     */
+    void onItemFocused(View view, boolean hasFocus);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
new file mode 100644
index 0000000..d027ede
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -0,0 +1,258 @@
+/*
+ * 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.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.
+ */
+public class FocusHighlightHelper {
+
+    static class FocusAnimator implements TimeAnimator.TimeListener {
+        private final View mView;
+        private final int mDuration;
+        private final ShadowOverlayContainer mWrapper;
+        private final float mScaleDiff;
+        private float mFocusLevel = 0f;
+        private float mFocusLevelStart;
+        private float mFocusLevelDelta;
+        private final TimeAnimator mAnimator = new TimeAnimator();
+        private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+        void animateFocus(boolean select, boolean immediate) {
+            endAnimation();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                setFocusLevel(end);
+            } else if (mFocusLevel != end) {
+                mFocusLevelStart = mFocusLevel;
+                mFocusLevelDelta = end - mFocusLevelStart;
+                mAnimator.start();
+            }
+        }
+
+        FocusAnimator(View view, float scale, int duration) {
+            mView = view;
+            mDuration = duration;
+            mScaleDiff = scale - 1f;
+            if (view instanceof ShadowOverlayContainer) {
+                mWrapper = (ShadowOverlayContainer) view;
+            } else {
+                mWrapper = null;
+            }
+            mAnimator.setTimeListener(this);
+        }
+
+        void setFocusLevel(float level) {
+            mFocusLevel = level;
+            float scale = 1f + mScaleDiff * level;
+            mView.setScaleX(scale);
+            mView.setScaleY(scale);
+            if (mWrapper != null) {
+                mWrapper.setShadowFocusLevel(level);
+            }
+        }
+
+        float getFocusLevel() {
+            return mFocusLevel;
+        }
+
+        void endAnimation() {
+            mAnimator.end();
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mDuration) {
+                fraction = 1;
+                mAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mDuration);
+            }
+            if (mInterpolator != null) {
+                fraction = mInterpolator.getInterpolation(fraction);
+            }
+            setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
+        }
+    }
+
+    static class BrowseItemFocusHighlight implements FocusHighlight {
+        private static final int DURATION_MS = 150;
+
+        private static float[] sScaleFactor = new float[4];
+
+        private int mScaleIndex;
+
+        BrowseItemFocusHighlight(int zoomIndex) {
+            mScaleIndex = (zoomIndex >= 0 && zoomIndex < sScaleFactor.length) ?
+                    zoomIndex : ZOOM_FACTOR_MEDIUM;
+        }
+
+        private static void lazyInit(Resources resources) {
+            if (sScaleFactor[ZOOM_FACTOR_NONE] == 0f) {
+                sScaleFactor[ZOOM_FACTOR_NONE] = 1f;
+                sScaleFactor[ZOOM_FACTOR_SMALL] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_small, 1, 1);
+                sScaleFactor[ZOOM_FACTOR_MEDIUM] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1);
+                sScaleFactor[ZOOM_FACTOR_LARGE] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_large, 1, 1);
+            }
+        }
+
+        private float getScale(View view) {
+            lazyInit(view.getResources());
+            return sScaleFactor[mScaleIndex];
+        }
+
+        private void viewFocused(View view, boolean hasFocus) {
+            view.setSelected(hasFocus);
+            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
+            if (animator == null) {
+                animator = new FocusAnimator(view, getScale(view), DURATION_MS);
+                view.setTag(R.id.lb_focus_animator, animator);
+            }
+            animator.animateFocus(hasFocus, false);
+        }
+
+        @Override
+        public void onItemFocused(View view, boolean hasFocus) {
+            viewFocused(view, hasFocus);
+        }
+    }
+
+    private static ActionItemFocusHighlight sActionItemFocusHighlight =
+            new ActionItemFocusHighlight();
+
+    /**
+     * Setup the focus highlight behavior of a focused item in browse list row.
+     * @param adapter  adapter of the list row.
+     */
+    public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex) {
+        adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex));
+    }
+
+    /**
+     * Setup the focus highlight behavior of a focused item in header list.
+     * @param gridView  the header list.
+     */
+    public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
+        if (gridView.getAdapter() instanceof ItemBridgeAdapter) {
+            ((ItemBridgeAdapter) gridView.getAdapter())
+                    .setFocusHighlight(new HeaderItemFocusHighlight(gridView));
+        }
+    }
+
+    /**
+     * Setup the focus highlight behavior of a focused item in an action list.
+     * @param adapter  adapter of the action list.
+     */
+    public static void setupActionItemFocusHighlight(ItemBridgeAdapter adapter) {
+        adapter.setFocusHighlight(sActionItemFocusHighlight);
+    }
+
+    static class HeaderItemFocusHighlight implements FocusHighlight {
+        private static boolean sInitialized;
+        private static float sSelectScale;
+        private static int sDuration;
+        private BaseGridView mGridView;
+
+        HeaderItemFocusHighlight(BaseGridView gridView) {
+            mGridView = gridView;
+            lazyInit(gridView.getContext().getResources());
+        }
+
+        private static void lazyInit(Resources res) {
+            if (!sInitialized) {
+                sSelectScale =
+                        Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale));
+                sDuration =
+                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
+                sInitialized = true;
+            }
+        }
+
+        class HeaderFocusAnimator extends FocusAnimator {
+
+            ItemBridgeAdapter.ViewHolder mViewHolder;
+            HeaderFocusAnimator(View view, float scale, int duration) {
+                super(view, scale, duration);
+                mViewHolder = (ItemBridgeAdapter.ViewHolder) mGridView.getChildViewHolder(view);
+            }
+
+            @Override
+            void setFocusLevel(float level) {
+                Presenter presenter = mViewHolder.getPresenter();
+                if (presenter instanceof RowHeaderPresenter) {
+                    ((RowHeaderPresenter) presenter).setSelectLevel(
+                            ((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
+                }
+                super.setFocusLevel(level);
+            }
+
+        }
+
+        private void viewFocused(View view, boolean hasFocus) {
+            view.setSelected(hasFocus);
+            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
+            if (animator == null) {
+                animator = new HeaderFocusAnimator(view, sSelectScale, sDuration);
+                view.setTag(R.id.lb_focus_animator, animator);
+            }
+            animator.animateFocus(hasFocus, false);
+        }
+
+        @Override
+        public void onItemFocused(View view, boolean hasFocus) {
+            viewFocused(view, hasFocus);
+        }
+    }
+
+    private static class ActionItemFocusHighlight implements FocusHighlight {
+        private boolean mInitialized;
+        private int mDuration;
+
+        private void initializeDimensions(Resources res) {
+            if (!mInitialized) {
+                mDuration = Integer.parseInt(res.getString(R.dimen.lb_details_overview_action_select_duration));
+            }
+        }
+
+        @Override
+        public void onItemFocused(View view, boolean hasFocus) {
+            initializeDimensions(view.getResources());
+            TransitionDrawable td = (TransitionDrawable) view.getBackground();
+            if (hasFocus) {
+                td.startTransition(mDuration);
+            } else {
+                td.reverseTransition(mDuration);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
new file mode 100644
index 0000000..0ad0669
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -0,0 +1,2016 @@
+/*
+ * 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.animation.TimeAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.Recycler;
+
+import static android.support.v7.widget.RecyclerView.NO_ID;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GridLayoutManager extends RecyclerView.LayoutManager {
+
+     /*
+      * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
+      * The class currently does three internal jobs:
+      * - Saves optical bounds insets.
+      * - Caches focus align view center.
+      * - Manages child view layout animation.
+      */
+    static class LayoutParams extends RecyclerView.LayoutParams {
+
+        // The view is saved only during animation.
+        private View mView;
+
+        // For placement
+        private int mLeftInset;
+        private int mTopInset;
+        private int mRighInset;
+        private int mBottomInset;
+
+        // For alignment
+        private int mAlignX;
+        private int mAlignY;
+
+        // For animations
+        private TimeAnimator mAnimator;
+        private long mDuration;
+        private boolean mFirstAttached;
+        // current virtual view position (scrollOffset + left/top) in the GridLayoutManager
+        private int mViewX, mViewY;
+        // animation start value of translation x and y
+        private float mAnimationStartTranslationX, mAnimationStartTranslationY;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(RecyclerView.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        void onViewAttached() {
+            endAnimate();
+            mFirstAttached = true;
+        }
+
+        void onViewDetached() {
+            endAnimate();
+        }
+
+        int getAlignX() {
+            return mAlignX;
+        }
+
+        int getAlignY() {
+            return mAlignY;
+        }
+
+        int getOpticalLeft(View view) {
+            return view.getLeft() + mLeftInset;
+        }
+
+        int getOpticalTop(View view) {
+            return view.getTop() + mTopInset;
+        }
+
+        int getOpticalRight(View view) {
+            return view.getRight() - mRighInset;
+        }
+
+        int getOpticalBottom(View view) {
+            return view.getBottom() - mBottomInset;
+        }
+
+        int getOpticalWidth(View view) {
+            return view.getWidth() - mLeftInset - mRighInset;
+        }
+
+        int getOpticalHeight(View view) {
+            return view.getHeight() - mTopInset - mBottomInset;
+        }
+
+        void setAlignX(int alignX) {
+            mAlignX = alignX;
+        }
+
+        void setAlignY(int alignY) {
+            mAlignY = alignY;
+        }
+
+        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
+            mLeftInset = leftInset;
+            mTopInset = topInset;
+            mRighInset = rightInset;
+            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);
+                    mView.setTranslationX(fractionToEnd * mAnimationStartTranslationX);
+                    mView.setTranslationY(fractionToEnd * mAnimationStartTranslationY);
+                    invalidateItemDecoration();
+                }
+            }
+        };
+
+        void startAnimate(GridLayoutManager layout, View view, long startDelay) {
+            if (mAnimator == null) {
+                mAnimator = new TimeAnimator();
+                mAnimator.setTimeListener(mTimeListener);
+            }
+            if (mFirstAttached) {
+                // first time record the initial location and return without animation
+                // TODO do we need initial animation?
+                mViewX = layout.getScrollOffsetX() + getOpticalLeft(view);
+                mViewY = layout.getScrollOffsetY() + getOpticalTop(view);
+                mFirstAttached = false;
+                return;
+            }
+            mView = view;
+            int newViewX = layout.getScrollOffsetX() + getOpticalLeft(mView);
+            int newViewY = layout.getScrollOffsetY() + getOpticalTop(mView);
+            if (newViewX != mViewX || newViewY != mViewY) {
+                mAnimator.cancel();
+                mAnimationStartTranslationX = mView.getTranslationX();
+                mAnimationStartTranslationY = mView.getTranslationY();
+                mAnimationStartTranslationX += mViewX - newViewX;
+                mAnimationStartTranslationY += mViewY - newViewY;
+                mDuration = layout.getChildLayoutAnimationDuration();
+                mAnimator.setDuration(mDuration);
+                mAnimator.setInterpolator(layout.getChildLayoutAnimationInterpolator());
+                mAnimator.setStartDelay(startDelay);
+                mAnimator.start();
+                mViewX = newViewX;
+                mViewY = newViewY;
+            }
+        }
+
+        void endAnimate() {
+            if (mAnimator != null) {
+                mAnimator.end();
+            }
+            if (mView != null) {
+                mView.setTranslationX(0);
+                mView.setTranslationY(0);
+                mView = null;
+            }
+        }
+
+        private void invalidateItemDecoration() {
+            ViewParent parent = mView.getParent();
+            if (parent instanceof RecyclerView) {
+                // TODO: we only need invalidate parent if it has ItemDecoration
+                ((RecyclerView) parent).invalidate();
+            }
+        }
+    }
+
+    private static final String TAG = "GridLayoutManager";
+    private static final boolean DEBUG = false;
+
+    private static final Interpolator sDefaultAnimationChildLayoutInterpolator
+            = new DecelerateInterpolator();
+
+    private static final long DEFAULT_CHILD_ANIMATION_DURATION_MS = 250;
+
+    private String getTag() {
+        return TAG + ":" + mBaseGridView.getId();
+    }
+
+    private final BaseGridView mBaseGridView;
+
+    /**
+     * The orientation of a "row".
+     */
+    private int mOrientation = HORIZONTAL;
+
+    private RecyclerView.Adapter mAdapter;
+    private RecyclerView.Recycler mRecycler;
+
+    private boolean mInLayout = false;
+
+    private OnChildSelectedListener mChildSelectedListener = null;
+
+    /**
+     * The focused position, it's not the currently visually aligned position
+     * but it is the final position that we intend to focus on. If there are
+     * multiple setSelection() called, mFocusPosition saves last value.
+     */
+    private int mFocusPosition = NO_POSITION;
+
+    /**
+     * Force a full layout under certain situations.
+     */
+    private boolean mForceFullLayout;
+
+    /**
+     * The scroll offsets of the viewport relative to the entire view.
+     */
+    private int mScrollOffsetPrimary;
+    private int mScrollOffsetSecondary;
+
+    /**
+     * User-specified fixed size of each grid item in the secondary direction, can be
+     * 0 to be determined by parent size and number of rows.
+     */
+    private int mItemLengthSecondaryRequested;
+    /**
+     * The fixed size of each grid item in the secondary direction. This corresponds to
+     * the row height, equal for all rows. Grid items may have variable length
+     * in the primary direction.
+     *
+     */
+    private int mItemLengthSecondary;
+
+    /**
+     * Margin between items.
+     */
+    private int mHorizontalMargin;
+    /**
+     * Margin between items vertically.
+     */
+    private int mVerticalMargin;
+    /**
+     * Margin in main direction.
+     */
+    private int mMarginPrimary;
+    /**
+     * Margin in second direction.
+     */
+    private int mMarginSecondary;
+    /**
+     * How to position child in secondary direction.
+     */
+    private int mGravity = Gravity.LEFT | Gravity.TOP;
+    /**
+     * The number of rows in the grid.
+     */
+    private int mNumRows;
+    /**
+     * Number of rows requested, can be 0 to be determined by parent size and
+     * rowHeight.
+     */
+    private int mNumRowsRequested = 1;
+
+    /**
+     * Tracking start/end position of each row for visible items.
+     */
+    private StaggeredGrid.Row[] mRows;
+
+    /**
+     * Saves grid information of each view.
+     */
+    private StaggeredGrid mGrid;
+    /**
+     * Position of first item (included) that has attached views.
+     */
+    private int mFirstVisiblePos;
+    /**
+     * Position of last item (included) that has attached views.
+     */
+    private int mLastVisiblePos;
+
+    /**
+     * Focus Scroll strategy.
+     */
+    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
+    /**
+     * Defines how item view is aligned in the window.
+     */
+    private final WindowAlignment mWindowAlignment = new WindowAlignment();
+
+    /**
+     * Defines how item view is aligned.
+     */
+    private final ItemAlignment mItemAlignment = new ItemAlignment();
+
+    /**
+     * Dimensions of the view, width or height depending on orientation.
+     */
+    private int mSizePrimary;
+
+    /**
+     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
+     *  default is false.
+     */
+    private boolean mFocusOutFront;
+
+    /**
+     * Allow DPAD key to navigate out at the end of the view, default is false.
+     */
+    private boolean mFocusOutEnd;
+
+    /**
+     * True if focus search is disabled.
+     */
+    private boolean mFocusSearchDisabled;
+
+    /**
+     * Animate layout changes from a child resizing or adding/removing a child.
+     */
+    private boolean mAnimateChildLayout = true;
+
+    /**
+     * Interpolator used to animate layout of children.
+     */
+    private Interpolator mAnimateLayoutChildInterpolator = sDefaultAnimationChildLayoutInterpolator;
+
+    /**
+     * Duration used to animate layout of children.
+     */
+    private long mAnimateLayoutChildDuration = DEFAULT_CHILD_ANIMATION_DURATION_MS;
+
+    public GridLayoutManager(BaseGridView baseGridView) {
+        mBaseGridView = baseGridView;
+    }
+
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
+            return;
+        }
+
+        mOrientation = orientation;
+        mWindowAlignment.setOrientation(orientation);
+        mItemAlignment.setOrientation(orientation);
+        mForceFullLayout = true;
+    }
+
+    public int getFocusScrollStrategy() {
+        return mFocusScrollStrategy;
+    }
+
+    public void setFocusScrollStrategy(int focusScrollStrategy) {
+        mFocusScrollStrategy = focusScrollStrategy;
+    }
+
+    public void setWindowAlignment(int windowAlignment) {
+        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
+    }
+
+    public int getWindowAlignment() {
+        return mWindowAlignment.mainAxis().getWindowAlignment();
+    }
+
+    public void setWindowAlignmentOffset(int alignmentOffset) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
+    }
+
+    public int getWindowAlignmentOffset() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
+    }
+
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
+    }
+
+    public float getWindowAlignmentOffsetPercent() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentOffset(int alignmentOffset) {
+        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentOffset() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffset();
+    }
+
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
+        updateChildAlignments();
+    }
+
+    public float getItemAlignmentOffsetPercent() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentViewId(int viewId) {
+        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentViewId() {
+        return mItemAlignment.mainAxis().getItemAlignmentViewId();
+    }
+
+    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
+        mFocusOutFront = throughFront;
+        mFocusOutEnd = throughEnd;
+    }
+
+    public void setNumRows(int numRows) {
+        if (numRows < 0) throw new IllegalArgumentException();
+        mNumRowsRequested = numRows;
+        mForceFullLayout = true;
+    }
+
+    public void setRowHeight(int height) {
+        if (height < 0) throw new IllegalArgumentException();
+        mItemLengthSecondaryRequested = height;
+    }
+
+    public void setItemMargin(int margin) {
+        mVerticalMargin = mHorizontalMargin = margin;
+        mMarginPrimary = mMarginSecondary = margin;
+    }
+
+    public void setVerticalMargin(int margin) {
+        if (mOrientation == HORIZONTAL) {
+            mMarginSecondary = mVerticalMargin = margin;
+        } else {
+            mMarginPrimary = mVerticalMargin = margin;
+        }
+    }
+
+    public void setHorizontalMargin(int margin) {
+        if (mOrientation == HORIZONTAL) {
+            mMarginPrimary = mHorizontalMargin = margin;
+        } else {
+            mMarginSecondary = mHorizontalMargin = margin;
+        }
+    }
+
+    public int getVerticalMargin() {
+        return mVerticalMargin;
+    }
+
+    public int getHorizontalMargin() {
+        return mHorizontalMargin;
+    }
+
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
+    protected boolean hasDoneFirstLayout() {
+        return mGrid != null;
+    }
+
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mChildSelectedListener = listener;
+    }
+
+    private int getPositionByView(View view) {
+        return getPositionByIndex(mBaseGridView.indexOfChild(view));
+    }
+
+    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;
+    }
+
+    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);
+        }
+    }
+
+    @Override
+    public boolean canScrollHorizontally() {
+        // We can scroll horizontally if we have horizontal orientation, or if
+        // we are vertical and have more than one column.
+        return mOrientation == HORIZONTAL || mNumRows > 1;
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        // We can scroll vertically if we have vertical orientation, or if we
+        // are horizontal and have more than one row.
+        return mOrientation == VERTICAL || mNumRows > 1;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
+        return new LayoutParams(context, attrs);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof RecyclerView.LayoutParams) {
+            return new LayoutParams((RecyclerView.LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    protected View getViewForPosition(int position) {
+        View v = mRecycler.getViewForPosition(mAdapter, position);
+        if (v != null) {
+            ((LayoutParams) v.getLayoutParams()).onViewAttached();
+        }
+        return v;
+    }
+
+    final int getOpticalLeft(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
+    }
+
+    final int getOpticalRight(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
+    }
+
+    final int getOpticalTop(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
+    }
+
+    final int getOpticalBottom(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
+    }
+
+    private int getViewMin(View v) {
+        return (mOrientation == HORIZONTAL) ? getOpticalLeft(v) : getOpticalTop(v);
+    }
+
+    private int getViewMax(View v) {
+        return (mOrientation == HORIZONTAL) ? getOpticalRight(v) : getOpticalBottom(v);
+    }
+
+    private int getViewCenter(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
+    }
+
+    private int getViewCenterSecondary(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
+    }
+
+    private int getViewCenterX(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalLeft(v) + p.getAlignX();
+    }
+
+    private int getViewCenterY(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalTop(v) + p.getAlignY();
+    }
+
+    /**
+     * Re-initialize data structures for a data change or handling invisible
+     * selection. The method tries its best to preserve position information so
+     * that staggered grid looks same before and after re-initialize.
+     * @param focusPosition The initial focusPosition that we would like to
+     *        focus on.
+     * @return Actual position that can be focused on.
+     */
+    private int init(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
+            int focusPosition) {
+
+        final int newItemCount = adapter.getItemCount();
+
+        if (focusPosition == NO_POSITION && newItemCount > 0) {
+            // if focus position is never set before,  initialize it to 0
+            focusPosition = 0;
+        }
+        // If adapter has changed then caches are invalid; otherwise,
+        // we try to maintain each row's position if number of rows keeps the same
+        // and existing mGrid contains the focusPosition.
+        if (mRows != null && mNumRows == mRows.length &&
+                mGrid != null && mGrid.getSize() > 0 && focusPosition >= 0 &&
+                focusPosition >= mGrid.getFirstIndex() &&
+                focusPosition <= mGrid.getLastIndex()) {
+            // strip mGrid to a subset (like a column) that contains focusPosition
+            mGrid.stripDownTo(focusPosition);
+            // make sure that remaining items do not exceed new adapter size
+            int firstIndex = mGrid.getFirstIndex();
+            int lastIndex = mGrid.getLastIndex();
+            if (DEBUG) {
+                Log .v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex " + lastIndex);
+            }
+            for (int i = lastIndex; i >=firstIndex; i--) {
+                if (i >= newItemCount) {
+                    mGrid.removeLast();
+                }
+            }
+            if (mGrid.getSize() == 0) {
+                focusPosition = newItemCount - 1;
+                // initialize row start locations
+                for (int i = 0; i < mNumRows; i++) {
+                    mRows[i].low = 0;
+                    mRows[i].high = 0;
+                }
+                if (DEBUG) Log.v(getTag(), "mGrid zero size");
+            } else {
+                // initialize row start locations
+                for (int i = 0; i < mNumRows; i++) {
+                    mRows[i].low = Integer.MAX_VALUE;
+                    mRows[i].high = Integer.MIN_VALUE;
+                }
+                firstIndex = mGrid.getFirstIndex();
+                lastIndex = mGrid.getLastIndex();
+                if (focusPosition > lastIndex) {
+                    focusPosition = mGrid.getLastIndex();
+                }
+                if (DEBUG) {
+                    Log.v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex "
+                        + lastIndex + " focusPosition " + focusPosition);
+                }
+                // fill rows with minimal view positions of the subset
+                for (int i = firstIndex; i <= lastIndex; i++) {
+                    View v = getViewByPosition(i);
+                    if (v == null) {
+                        continue;
+                    }
+                    int row = mGrid.getLocation(i).row;
+                    int low = getViewMin(v) + mScrollOffsetPrimary;
+                    if (low < mRows[row].low) {
+                        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;
+                    }
+                }
+            }
+
+            // Same adapter, we can reuse any attached views
+            detachAndScrapAttachedViews(recycler);
+
+        } else {
+            // otherwise recreate data structure
+            mRows = new StaggeredGrid.Row[mNumRows];
+            for (int i = 0; i < mNumRows; i++) {
+                mRows[i] = new StaggeredGrid.Row();
+            }
+            mGrid = new StaggeredGridDefault();
+            if (newItemCount == 0) {
+                focusPosition = NO_POSITION;
+            } else if (focusPosition >= newItemCount) {
+                focusPosition = newItemCount - 1;
+            }
+
+            // Adapter may have changed so remove all attached views permanently
+            removeAllViews();
+
+            mScrollOffsetPrimary = 0;
+            mScrollOffsetSecondary = 0;
+            mWindowAlignment.reset();
+        }
+
+        mAdapter = adapter;
+        mRecycler = recycler;
+        mGrid.setProvider(mGridProvider);
+        // mGrid share the same Row array information
+        mGrid.setRows(mRows);
+        mFirstVisiblePos = mLastVisiblePos = NO_POSITION;
+
+        initScrollController();
+
+        return focusPosition;
+    }
+
+    @Override
+    public void onMeasure(int widthSpec, int heightSpec) {
+        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
+        int measuredSizeSecondary;
+        if (mOrientation == HORIZONTAL) {
+            sizePrimary = MeasureSpec.getSize(widthSpec);
+            sizeSecondary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(heightSpec);
+            paddingSecondary = getPaddingTop() + getPaddingBottom();
+        } else {
+            sizeSecondary = MeasureSpec.getSize(widthSpec);
+            sizePrimary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(widthSpec);
+            paddingSecondary = getPaddingLeft() + getPaddingRight();
+        }
+        switch (modeSecondary) {
+        case MeasureSpec.UNSPECIFIED:
+            if (mItemLengthSecondaryRequested == 0) {
+                if (mOrientation == HORIZONTAL) {
+                    throw new IllegalStateException("Must specify rowHeight or view height");
+                } else {
+                    throw new IllegalStateException("Must specify columnWidth or view width");
+                }
+            }
+            mItemLengthSecondary = mItemLengthSecondaryRequested;
+            if (mNumRowsRequested == 0) {
+                mNumRows = 1;
+            } else {
+                mNumRows = mNumRowsRequested;
+            }
+            measuredSizeSecondary = mItemLengthSecondary * mNumRows + mMarginSecondary
+                * (mNumRows - 1) + paddingSecondary;
+            break;
+        case MeasureSpec.AT_MOST:
+        case MeasureSpec.EXACTLY:
+            if (mNumRowsRequested == 0 && mItemLengthSecondaryRequested == 0) {
+                mNumRows = 1;
+                mItemLengthSecondary = sizeSecondary - paddingSecondary;
+            } else if (mNumRowsRequested == 0) {
+                mItemLengthSecondary = mItemLengthSecondaryRequested;
+                mNumRows = (sizeSecondary + mMarginSecondary)
+                    / (mItemLengthSecondaryRequested + mMarginSecondary);
+            } else if (mItemLengthSecondaryRequested == 0) {
+                mNumRows = mNumRowsRequested;
+                mItemLengthSecondary = (sizeSecondary - paddingSecondary - mMarginSecondary
+                        * (mNumRows - 1)) / mNumRows;
+            } else {
+                mNumRows = mNumRowsRequested;
+                mItemLengthSecondary = mItemLengthSecondaryRequested;
+            }
+            measuredSizeSecondary = sizeSecondary;
+            if (modeSecondary == MeasureSpec.AT_MOST) {
+                int childrenSize = mItemLengthSecondary * mNumRows + mMarginSecondary
+                    * (mNumRows - 1) + paddingSecondary;
+                if (childrenSize < measuredSizeSecondary) {
+                    measuredSizeSecondary = childrenSize;
+                }
+            }
+            break;
+        default:
+            throw new IllegalStateException("wrong spec");
+        }
+        if (mOrientation == HORIZONTAL) {
+            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
+        } else {
+            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
+        }
+        if (DEBUG) {
+            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary +
+                    " measuredSizeSecondary " + measuredSizeSecondary +
+                    " mItemLengthSecondary " + mItemLengthSecondary +
+                    " mNumRows " + mNumRows);
+        }
+    }
+
+    private void measureChild(View child) {
+        final ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+        int widthSpec, heightSpec;
+        if (mOrientation == HORIZONTAL) {
+            widthSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, lp.width);
+            heightSpec = ViewGroup.getChildMeasureSpec(MeasureSpec.makeMeasureSpec(
+                    mItemLengthSecondary, MeasureSpec.EXACTLY), 0, lp.height);
+        } else {
+            heightSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, lp.height);
+            widthSpec = ViewGroup.getChildMeasureSpec(MeasureSpec.makeMeasureSpec(
+                    mItemLengthSecondary, MeasureSpec.EXACTLY), 0, lp.width);
+        }
+
+        child.measure(widthSpec, heightSpec);
+    }
+
+    private StaggeredGrid.Provider mGridProvider = new StaggeredGrid.Provider() {
+
+        @Override
+        public int getCount() {
+            return mAdapter.getItemCount();
+        }
+
+        @Override
+        public void createItem(int index, int rowIndex, boolean append) {
+            View v = getViewForPosition(index);
+            if (mFirstVisiblePos >= 0) {
+                // when StaggeredGrid append or prepend item, we must guarantee
+                // that sibling item has created views already.
+                if (append && index != mLastVisiblePos + 1) {
+                    throw new RuntimeException();
+                } else if (!append && index != mFirstVisiblePos - 1) {
+                    throw new RuntimeException();
+                }
+            }
+
+            if (append) {
+                addView(v);
+            } else {
+                addView(v, 0);
+            }
+
+            measureChild(v);
+
+            int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
+            int start, end;
+            if (append) {
+                start = mRows[rowIndex].high;
+                if (start != mRows[rowIndex].low) {
+                    // if there are existing item in the row,  add margin between
+                    start += mMarginPrimary;
+                } else {
+                    final int lastRow = mRows.length - 1;
+                    if (lastRow != rowIndex && mRows[lastRow].high != mRows[lastRow].low) {
+                        // if there are existing item in the last row, insert
+                        // the new item after the last item of last row.
+                        start = mRows[lastRow].high + mMarginPrimary;
+                    }
+                }
+                end = start + length;
+                mRows[rowIndex].high = end;
+            } else {
+                end = mRows[rowIndex].low;
+                if (end != mRows[rowIndex].high) {
+                    end -= mMarginPrimary;
+                } else if (0 != rowIndex && mRows[0].high != mRows[0].low) {
+                    // if there are existing item in the first row, insert
+                    // the new item before the first item of first row.
+                    end = mRows[0].low - mMarginPrimary;
+                }
+                start = end - length;
+                mRows[rowIndex].low = start;
+            }
+            if (mFirstVisiblePos < 0) {
+                mFirstVisiblePos = mLastVisiblePos = index;
+            } else {
+                if (append) {
+                    mLastVisiblePos++;
+                } else {
+                    mFirstVisiblePos--;
+                }
+            }
+            int startSecondary = rowIndex * (mItemLengthSecondary + mMarginSecondary);
+            layoutChild(v, start - mScrollOffsetPrimary, end - mScrollOffsetPrimary,
+                    startSecondary - mScrollOffsetSecondary);
+            if (DEBUG) {
+                Log.d(getTag(), "addView " + index + " " + v);
+            }
+            updateScrollMin();
+            updateScrollMax();
+        }
+    };
+
+    private void layoutChild(View v, int start, int end, int startSecondary) {
+         int measuredSecondary = mOrientation == HORIZONTAL ? v.getMeasuredHeight()
+                 : v.getMeasuredWidth();
+        if (measuredSecondary > mItemLengthSecondary) {
+           measuredSecondary = mItemLengthSecondary;
+        }
+        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int horizontalGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
+            // do nothing
+        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT) {
+            startSecondary += mItemLengthSecondary - measuredSecondary;
+        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL) {
+            startSecondary += (mItemLengthSecondary - measuredSecondary) / 2;
+        }
+        int left, top, right, bottom;
+        if (mOrientation == HORIZONTAL) {
+            left = start;
+            top = startSecondary;
+            right = end;
+            bottom = startSecondary + measuredSecondary;
+        } else {
+            top = start;
+            left = startSecondary;
+            bottom = end;
+            right = startSecondary + measuredSecondary;
+        }
+        v.layout(left, top, right, bottom);
+        updateChildOpticalInsets(v, left, top, right, bottom);
+        updateChildAlignments(v);
+    }
+
+    private void updateChildOpticalInsets(View v, int left, int top, int right, int bottom) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        p.setOpticalInsets(left - v.getLeft(), top - v.getTop(),
+                v.getRight() - right, v.getBottom() - bottom);
+    }
+
+    private void updateChildAlignments(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
+        p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
+    }
+
+    private void updateChildAlignments() {
+        for (int i = 0, c = getChildCount(); i < c; i++) {
+            updateChildAlignments(getChildAt(i));
+        }
+    }
+
+    private boolean needsAppendVisibleItem() {
+        if (mLastVisiblePos < mFocusPosition) {
+            return true;
+        }
+        int right = mScrollOffsetPrimary + mSizePrimary;
+        for (int i = 0; i < mNumRows; i++) {
+            if (mRows[i].low == mRows[i].high) {
+                if (mRows[i].high < right) {
+                    return true;
+                }
+            } else if (mRows[i].high < right - mMarginPrimary) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean needsPrependVisibleItem() {
+        if (mFirstVisiblePos > mFocusPosition) {
+            return true;
+        }
+        for (int i = 0; i < mNumRows; i++) {
+            if (mRows[i].low == mRows[i].high) {
+                if (mRows[i].low > mScrollOffsetPrimary) {
+                    return true;
+                }
+            } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Append one column if possible and return true if reach end.
+    private boolean appendOneVisibleItem() {
+        while (true) {
+            if (mLastVisiblePos != NO_POSITION && mLastVisiblePos < mAdapter.getItemCount() -1 &&
+                    mLastVisiblePos < mGrid.getLastIndex()) {
+                // append invisible view of saved location till last row
+                final int index = mLastVisiblePos + 1;
+                final int row = mGrid.getLocation(index).row;
+                mGridProvider.createItem(index, row, true);
+                if (row == mNumRows - 1) {
+                    return false;
+                }
+            } else if ((mLastVisiblePos == NO_POSITION && mAdapter.getItemCount() > 0) ||
+                    (mLastVisiblePos != NO_POSITION &&
+                            mLastVisiblePos < mAdapter.getItemCount() - 1)) {
+                mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
+    private void appendVisibleItems() {
+        while (needsAppendVisibleItem()) {
+            if (appendOneVisibleItem()) {
+                break;
+            }
+        }
+    }
+
+    // Prepend one column if possible and return true if reach end.
+    private boolean prependOneVisibleItem() {
+        while (true) {
+            if (mFirstVisiblePos > 0) {
+                if (mFirstVisiblePos > mGrid.getFirstIndex()) {
+                    // prepend invisible view of saved location till first row
+                    final int index = mFirstVisiblePos - 1;
+                    final int row = mGrid.getLocation(index).row;
+                    mGridProvider.createItem(index, row, false);
+                    if (row == 0) {
+                        return false;
+                    }
+                } else {
+                    mGrid.prependItems(mScrollOffsetPrimary);
+                    return false;
+                }
+            } else {
+                return true;
+            }
+        }
+    }
+
+    private void prependVisibleItems() {
+        while (needsPrependVisibleItem()) {
+            if (prependOneVisibleItem()) {
+                break;
+            }
+        }
+    }
+
+    private void removeChildAt(int position) {
+        View v = getViewByPosition(position);
+        if (v != null) {
+            if (DEBUG) {
+                Log.d(getTag(), "removeAndRecycleViewAt " + position);
+            }
+            ((LayoutParams) v.getLayoutParams()).onViewDetached();
+            removeAndRecycleViewAt(getIndexByPosition(position), mRecycler);
+        }
+    }
+
+    private void removeInvisibleViewsAtEnd() {
+        boolean update = false;
+        while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
+            View view = getViewByPosition(mLastVisiblePos);
+            if (getViewMin(view) > mSizePrimary) {
+                removeChildAt(mLastVisiblePos);
+                mLastVisiblePos--;
+                update = true;
+            } else {
+                break;
+            }
+        }
+        if (update) {
+            updateRowsMinMax();
+        }
+    }
+
+    private void removeInvisibleViewsAtFront() {
+        boolean update = false;
+        while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
+            View view = getViewByPosition(mFirstVisiblePos);
+            if (getViewMax(view) < 0) {
+                removeChildAt(mFirstVisiblePos);
+                mFirstVisiblePos++;
+                update = true;
+            } else {
+                break;
+            }
+        }
+        if (update) {
+            updateRowsMinMax();
+        }
+    }
+
+    private void updateRowsMinMax() {
+        if (mFirstVisiblePos < 0) {
+            return;
+        }
+        for (int i = 0; i < mNumRows; i++) {
+            mRows[i].low = Integer.MAX_VALUE;
+            mRows[i].high = Integer.MIN_VALUE;
+        }
+        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+            View view = getViewByPosition(i);
+            int row = mGrid.getLocation(i).row;
+            int low = getViewMin(view) + mScrollOffsetPrimary;
+            if (low < mRows[row].low) {
+                mRows[row].low = low;
+            }
+            int high = getViewMax(view) + mScrollOffsetPrimary;
+            if (high > mRows[row].high) {
+                mRows[row].high = high;
+            }
+        }
+    }
+
+    /**
+     * Relayout and re-positioning child for a possible new size and/or a new
+     * start.
+     *
+     * @param view View to measure and layout.
+     * @param start New start of the view or Integer.MIN_VALUE for not change.
+     * @return New start of next view.
+     */
+    private int updateChildView(View view, int start, int startSecondary) {
+        if (start == Integer.MIN_VALUE) {
+            start = getViewMin(view);
+        }
+        int end;
+        if (mOrientation == HORIZONTAL) {
+            if (view.isLayoutRequested() || view.getMeasuredHeight() != mItemLengthSecondary) {
+                measureChild(view);
+            }
+            end = start + view.getMeasuredWidth();
+        } else {
+            if (view.isLayoutRequested() || view.getMeasuredWidth() != mItemLengthSecondary) {
+                measureChild(view);
+            }
+            end = start + view.getMeasuredHeight();
+        }
+
+        layoutChild(view, start, end, startSecondary);
+        return end + mMarginPrimary;
+    }
+
+    // Fast layout when there is no structure change, adapter change, etc.
+    protected void fastRelayout() {
+        initScrollController();
+
+        List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
+
+        // relayout and repositioning views on each row
+        for (int i = 0; i < mNumRows; i++) {
+            List<Integer> row = rows[i];
+            int start = Integer.MIN_VALUE;
+            int startSecondary =
+                i * (mItemLengthSecondary + mMarginSecondary) - mScrollOffsetSecondary;
+            for (int j = 0, size = row.size(); j < size; j++) {
+                int position = row.get(j);
+                start = updateChildView(getViewByPosition(position), start, startSecondary);
+            }
+        }
+
+        appendVisibleItems();
+        prependVisibleItems();
+
+        updateRowsMinMax();
+        updateScrollMin();
+        updateScrollMax();
+
+        if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+            View focusView = getViewByPosition(mFocusPosition == NO_POSITION ? 0 : mFocusPosition);
+            scrollToView(focusView, false);
+        }
+    }
+
+    // Lays out items based on the current scroll position
+    @Override
+    public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
+            boolean structureChanged, RecyclerView.State state) {
+        if (DEBUG) {
+            Log.v(getTag(), "layoutChildren start numRows " + mNumRows + " mScrollOffsetSecondary "
+                    + mScrollOffsetSecondary + " mScrollOffsetPrimary " + mScrollOffsetPrimary
+                    + " structureChanged " + structureChanged
+                    + " mForceFullLayout " + mForceFullLayout);
+            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
+        }
+
+        if (mNumRows == 0) {
+            // haven't done measure yet
+            return;
+        }
+        final int itemCount = adapter.getItemCount();
+        if (itemCount < 0) {
+            return;
+        }
+
+        mInLayout = true;
+
+        // Track the old focus view so we can adjust our system scroll position
+        // so that any scroll animations happening now will remain valid.
+        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;
+            }
+        }
+
+        boolean hasDoneFirstLayout = hasDoneFirstLayout();
+        if (!structureChanged && !mForceFullLayout && hasDoneFirstLayout) {
+            fastRelayout();
+        } else {
+            boolean hadFocus = mBaseGridView.hasFocus();
+
+            int newFocusPosition = init(adapter, recycler, mFocusPosition);
+            if (DEBUG) {
+                Log.v(getTag(), "mFocusPosition " + mFocusPosition + " newFocusPosition "
+                    + newFocusPosition);
+            }
+
+            // depending on result of init(), either recreating everything
+            // or try to reuse the row start positions near mFocusPosition
+            if (mGrid.getSize() == 0) {
+                // this is a fresh creating all items, starting from
+                // mFocusPosition with a estimated row index.
+                mGrid.setStart(newFocusPosition, StaggeredGrid.START_DEFAULT);
+
+                // Can't track the old focus view
+                delta = deltaSecondary = 0;
+
+            } else {
+                // mGrid remembers Locations for the column that
+                // contains mFocusePosition and also mRows remembers start
+                // positions of each row.
+                // Manually re-create child views for that column
+                int firstIndex = mGrid.getFirstIndex();
+                int lastIndex = mGrid.getLastIndex();
+                for (int i = firstIndex; i <= lastIndex; i++) {
+                    mGridProvider.createItem(i, mGrid.getLocation(i).row, true);
+                }
+            }
+            // add visible views at end until reach the end of window
+            appendVisibleItems();
+            // add visible views at front until reach the start of window
+            prependVisibleItems();
+            // multiple rounds: scrollToView of first round may drag first/last child into
+            // "visible window" and we update scrollMin/scrollMax then run second scrollToView
+            int oldFirstVisible;
+            int oldLastVisible;
+            do {
+                oldFirstVisible = mFirstVisiblePos;
+                oldLastVisible = mLastVisiblePos;
+                View focusView = getViewByPosition(newFocusPosition);
+                // we need force to initialize the child view's position
+                scrollToView(focusView, false);
+                if (focusView != null && hadFocus) {
+                    focusView.requestFocus();
+                }
+                appendVisibleItems();
+                prependVisibleItems();
+                removeInvisibleViewsAtFront();
+                removeInvisibleViewsAtEnd();
+            } while (mFirstVisiblePos != oldFirstVisible || mLastVisiblePos != oldLastVisible);
+        }
+        mForceFullLayout = false;
+
+        if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+            scrollDirectionPrimary(-delta);
+            scrollDirectionSecondary(-deltaSecondary);
+        }
+        appendVisibleItems();
+        prependVisibleItems();
+        removeInvisibleViewsAtFront();
+        removeInvisibleViewsAtEnd();
+
+        if (DEBUG) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            mGrid.debugPrint(pw);
+            Log.d(getTag(), sw.toString());
+        }
+
+        removeAndRecycleScrap(recycler);
+        attemptAnimateLayoutChild();
+
+        if (!hasDoneFirstLayout) {
+            dispatchChildSelected();
+        }
+        mInLayout = false;
+        if (DEBUG) Log.v(getTag(), "layoutChildren end");
+    }
+
+    private void offsetChildrenSecondary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == HORIZONTAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+        mScrollOffsetSecondary -= increment;
+    }
+
+    private void offsetChildrenPrimary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == VERTICAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+        mScrollOffsetPrimary -= increment;
+    }
+
+    @Override
+    public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler,
+            RecyclerView.State state) {
+        if (DEBUG) Log.v(TAG, "scrollHorizontallyBy " + dx);
+
+        if (mOrientation == HORIZONTAL) {
+            return scrollDirectionPrimary(dx);
+        } else {
+            return scrollDirectionSecondary(dx);
+        }
+    }
+
+    @Override
+    public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler,
+            RecyclerView.State state) {
+        if (DEBUG) Log.v(TAG, "scrollVerticallyBy " + dy);
+        if (mOrientation == VERTICAL) {
+            return scrollDirectionPrimary(dy);
+        } else {
+            return scrollDirectionSecondary(dy);
+        }
+    }
+
+    // scroll in main direction may add/prune views
+    private int scrollDirectionPrimary(int da) {
+        offsetChildrenPrimary(-da);
+        if (mInLayout) {
+            return da;
+        }
+        if (da > 0) {
+            appendVisibleItems();
+            removeInvisibleViewsAtFront();
+        } else if (da < 0) {
+            prependVisibleItems();
+            removeInvisibleViewsAtEnd();
+        }
+        attemptAnimateLayoutChild();
+        mBaseGridView.invalidate();
+        return da;
+    }
+
+    // scroll in second direction will not add/prune views
+    private int scrollDirectionSecondary(int dy) {
+        offsetChildrenSecondary(-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;
+                }
+            }
+            mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
+            if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge);
+        }
+    }
+
+    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;
+                }
+            }
+            mWindowAlignment.mainAxis().setMinEdge(minEdge);
+            if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge);
+        }
+    }
+
+    private void initScrollController() {
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+        mWindowAlignment.vertical.setSize(getHeight());
+        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+        mSizePrimary = mWindowAlignment.mainAxis().getSize();
+        mWindowAlignment.mainAxis().invalidateScrollMin();
+        mWindowAlignment.mainAxis().invalidateScrollMax();
+
+        // second axis min/max is determined at initialization, the mainAxis
+        // min/max is determined later when we scroll to first or last item
+        mWindowAlignment.secondAxis().setMinEdge(0);
+        mWindowAlignment.secondAxis().setMaxEdge(mItemLengthSecondary * mNumRows + mMarginSecondary
+                * (mNumRows - 1));
+
+        if (DEBUG) {
+            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary + " "
+                + " mItemLengthSecondary " + mItemLengthSecondary + " " + mWindowAlignment);
+        }
+    }
+
+    public void setSelection(RecyclerView parent, int position) {
+        setSelection(parent, position, false);
+    }
+
+    public void setSelectionSmooth(RecyclerView parent, int position) {
+        setSelection(parent, position, true);
+    }
+
+    public int getSelection() {
+        return mFocusPosition;
+    }
+
+    public void setSelection(RecyclerView parent, int position, boolean smooth) {
+        if (mFocusPosition == position) {
+            return;
+        }
+        View view = getViewByPosition(position);
+        if (view != null) {
+            scrollToView(view, smooth);
+        } else {
+            boolean right = position > mFocusPosition;
+            mFocusPosition = position;
+            if (smooth) {
+                if (!hasDoneFirstLayout()) {
+                    Log.w(getTag(), "setSelectionSmooth should " +
+                            "not be called before first layout pass");
+                    return;
+                }
+                if (right) {
+                    appendVisibleItems();
+                } else {
+                    prependVisibleItems();
+                }
+                scrollToView(getViewByPosition(position), smooth);
+            } else {
+                mForceFullLayout = true;
+                parent.requestLayout();
+            }
+        }
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        boolean needsLayout = false;
+        if (itemCount != 0) {
+            if (mFirstVisiblePos < 0) {
+                needsLayout = true;
+            } else if (!(positionStart > mLastVisiblePos + 1 ||
+                    positionStart + itemCount < mFirstVisiblePos - 1)) {
+                needsLayout = true;
+            }
+        }
+        if (needsLayout) {
+            recyclerView.requestLayout();
+        }
+    }
+
+    @Override
+    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+        if (mFocusSearchDisabled) {
+            return true;
+        }
+        if (!mInLayout) {
+            scrollToView(child, true);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
+            boolean immediate) {
+        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
+        return false;
+    }
+
+    int getScrollOffsetX() {
+        return mOrientation == HORIZONTAL ? mScrollOffsetPrimary : mScrollOffsetSecondary;
+    }
+
+    int getScrollOffsetY() {
+        return mOrientation == HORIZONTAL ? mScrollOffsetSecondary : mScrollOffsetPrimary;
+    }
+
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        int scrollOffsetX = getScrollOffsetX();
+        int scrollOffsetY = getScrollOffsetY();
+        int viewCenterX = scrollOffsetX + getViewCenterX(view);
+        int viewCenterY = scrollOffsetY + getViewCenterY(view);
+        offsets[0] = mWindowAlignment.horizontal.getSystemScrollPos(viewCenterX) - scrollOffsetX;
+        offsets[1] = mWindowAlignment.vertical.getSystemScrollPos(viewCenterY) - scrollOffsetY;
+    }
+
+    /**
+     * Scroll to a given child view and change mFocusPosition.
+     */
+    private void scrollToView(View view, boolean smooth) {
+        int newFocusPosition = getPositionByView(view);
+        if (mInLayout || newFocusPosition != mFocusPosition) {
+            mFocusPosition = newFocusPosition;
+            dispatchChildSelected();
+        }
+        if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
+            mBaseGridView.invalidate();
+        }
+        if (view == null) {
+            return;
+        }
+        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
+            // transfer focus to the child if it does not have focus yet (e.g. triggered
+            // by setSelection())
+            view.requestFocus();
+        }
+        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;
+        }
+    }
+
+    private void scrollItemOrPage(View view, boolean smooth) {
+        int pos = getPositionByView(view);
+        int viewMin = getViewMin(view);
+        int viewMax = getViewMax(view);
+        // we either align "firstView" to left/top padding edge
+        // or align "lastView" to right/bottom padding edge
+        View firstView = null;
+        View lastView = null;
+        int paddingLow = mWindowAlignment.mainAxis().getPaddingLow();
+        int clientSize = mWindowAlignment.mainAxis().getClientSize();
+        final int row = mGrid.getLocation(pos).row;
+        if (viewMin < paddingLow) {
+            // view enters low padding area:
+            firstView = view;
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll one "page" left/top,
+                // align first visible item of the "page" at the low padding edge.
+                while (!prependOneVisibleItem()) {
+                    List<Integer> positions =
+                            mGrid.getItemPositionsInRows(mFirstVisiblePos, pos)[row];
+                    firstView = getViewByPosition(positions.get(0));
+                    if (viewMax - getViewMin(firstView) > clientSize) {
+                        if (positions.size() > 1) {
+                            firstView = getViewByPosition(positions.get(1));
+                        }
+                        break;
+                    }
+                }
+            }
+        } else if (viewMax > clientSize + paddingLow) {
+            // view enters high padding area:
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll whole one page right/bottom, align view at the low padding edge.
+                firstView = view;
+                do {
+                    List<Integer> positions =
+                            mGrid.getItemPositionsInRows(pos, mLastVisiblePos)[row];
+                    lastView = getViewByPosition(positions.get(positions.size() - 1));
+                    if (getViewMax(lastView) - viewMin > clientSize) {
+                        lastView = null;
+                        break;
+                    }
+                } while (!appendOneVisibleItem());
+                if (lastView != null) {
+                    // however if we reached end,  we should align last view.
+                    firstView = null;
+                }
+            } else {
+                lastView = view;
+            }
+        }
+        int scrollPrimary = 0;
+        int scrollSecondary = 0;
+        if (firstView != null) {
+            scrollPrimary = getViewMin(firstView) - paddingLow;
+        } else if (lastView != null) {
+            scrollPrimary = getViewMax(lastView) - (paddingLow + clientSize);
+        }
+        View secondaryAlignedView;
+        if (firstView != null) {
+            secondaryAlignedView = firstView;
+        } else if (lastView != null) {
+            secondaryAlignedView = lastView;
+        } else {
+            secondaryAlignedView = view;
+        }
+        int viewCenterSecondary = mScrollOffsetSecondary +
+                getViewCenterSecondary(secondaryAlignedView);
+        mWindowAlignment.secondAxis().updateScrollCenter(viewCenterSecondary);
+        scrollSecondary = mWindowAlignment.secondAxis().getSystemScrollPos();
+        scrollSecondary -= mScrollOffsetSecondary;
+        scrollGrid(scrollPrimary, scrollSecondary, smooth);
+    }
+
+    private void scrollToAlignedPosition(View view, boolean smooth) {
+        int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
+        int viewCenterSecondary = mScrollOffsetSecondary + getViewCenterSecondary(view);
+        if (DEBUG) {
+            Log.v(getTag(), "scrollAligned smooth=" + smooth + " pos=" + mFocusPosition + " "
+                    + viewCenterPrimary +","+viewCenterSecondary + " " + 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);
+        }
+    }
+
+    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
+        if (mInLayout) {
+            scrollDirectionPrimary(scrollPrimary);
+            scrollDirectionSecondary(scrollSecondary);
+        } else {
+            int scrollX;
+            int scrollY;
+            if (mOrientation == HORIZONTAL) {
+                scrollX = scrollPrimary;
+                scrollY = scrollSecondary;
+            } else {
+                scrollX = scrollSecondary;
+                scrollY = scrollPrimary;
+            }
+            if (smooth) {
+                mBaseGridView.smoothScrollBy(scrollX, scrollY);
+            } else {
+                mBaseGridView.scrollBy(scrollX, scrollY);
+            }
+        }
+    }
+
+    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();
+            } else {
+                // record initial location values
+                p.mFirstAttached = true;
+                p.startAnimate(this, v, 0);
+            }
+        }
+    }
+
+    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(this, v, 0);
+        }
+    }
+
+    public boolean isChildLayoutAnimated() {
+        return mAnimateChildLayout;
+    }
+
+    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);
+            if (index >= 0) {
+                return index;
+            }
+            view = (View) view.getParent();
+        }
+        return NO_POSITION;
+    }
+
+    void setFocusSearchDisabled(boolean disabled) {
+        mFocusSearchDisabled = disabled;
+    }
+
+    boolean isFocusSearchDisabled() {
+        return mFocusSearchDisabled;
+    }
+
+    @Override
+    public View onInterceptFocusSearch(View focused, int direction) {
+        if (mFocusSearchDisabled) {
+            return focused;
+        }
+        return null;
+    }
+
+    @Override
+    public boolean onAddFocusables(RecyclerView recyclerView,
+            ArrayList<View> views, int direction, int focusableMode) {
+        if (mFocusSearchDisabled) {
+            return true;
+        }
+        // If this viewgroup or one of its children currently has focus then we
+        // consider our children for focus searching in main direction on the same row.
+        // If this viewgroup has no focus and using focus align, we want the system
+        // to ignore our children and pass focus to the viewgroup, which will pass
+        // focus on to its children appropriately.
+        // If this viewgroup has no focus and not using focus align, we want to
+        // consider the child that does not overlap with padding area.
+        if (recyclerView.hasFocus()) {
+            final int movement = getMovement(direction);
+            if (movement != PREV_ITEM && movement != NEXT_ITEM) {
+                // Move on secondary direction uses default addFocusables().
+                return false;
+            }
+            final View focused = recyclerView.findFocus();
+            final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
+            // Add focusables of focused item.
+            if (focusedPos != NO_POSITION) {
+                getViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
+            }
+            final int focusedRow = mGrid != null && focusedPos != NO_POSITION ?
+                    mGrid.getLocation(focusedPos).row : NO_POSITION;
+            // Add focusables of next neighbor of same row on the focus search direction.
+            if (mGrid != null) {
+                final int focusableCount = views.size();
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    int index = movement == NEXT_ITEM ? i : count - 1 - i;
+                    final View child = getChildAt(index);
+                    if (child.getVisibility() != View.VISIBLE) {
+                        continue;
+                    }
+                    int position = getPositionByIndex(index);
+                    StaggeredGrid.Location loc = mGrid.getLocation(position);
+                    if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
+                        if (focusedPos == NO_POSITION || 
+                                (movement == NEXT_ITEM && position > focusedPos)
+                                || (movement == PREV_ITEM && position < focusedPos)) {
+                            child.addFocusables(views,  direction, focusableMode);
+                            if (views.size() > focusableCount) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                // adding views not overlapping padding area to avoid scrolling in gaining focus
+                int left = mWindowAlignment.mainAxis().getPaddingLow();
+                int right = mWindowAlignment.mainAxis().getClientSize() + left;
+                int focusableCount = views.size();
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    View child = getChildAt(i);
+                    if (child.getVisibility() == View.VISIBLE) {
+                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                }
+                // if we cannot find any, then just add all children.
+                if (views.size() == focusableCount) {
+                    for (int i = 0, count = getChildCount(); i < count; i++) {
+                        View child = getChildAt(i);
+                        if (child.getVisibility() == View.VISIBLE) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                    if (views.size() != focusableCount) {
+                        return true;
+                    }
+                } else {
+                    return true;
+                }
+                // if still cannot find any, fall through and add itself
+            }
+            if (recyclerView.isFocusable()) {
+                views.add(recyclerView);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
+            Recycler recycler) {
+        if (DEBUG) Log.v(getTag(), "onFocusSearchFailed direction " + direction);
+
+        View view = null;
+        int movement = getMovement(direction);
+        final FocusFinder ff = FocusFinder.getInstance();
+        if (movement == NEXT_ITEM) {
+            while (view == null && !appendOneVisibleItem()) {
+                view = ff.findNextFocus(mBaseGridView, focused, direction);
+            }
+        } else if (movement == PREV_ITEM){
+            while (view == null && !prependOneVisibleItem()) {
+                view = ff.findNextFocus(mBaseGridView, focused, direction);
+            }
+        }
+        if (view == null) {
+            // returning the same view to prevent focus lost when scrolling past the end of the list
+            if (movement == PREV_ITEM) {
+                view = mFocusOutFront ? null : focused;
+            } else if (movement == NEXT_ITEM){
+                view = mFocusOutEnd ? null : focused;
+            }
+        }
+        if (DEBUG) Log.v(getTag(), "returning view " + view);
+        return view;
+    }
+
+    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
+            Rect previouslyFocusedRect) {
+        switch (mFocusScrollStrategy) {
+        case BaseGridView.FOCUS_SCROLL_ALIGNED:
+        default:
+            return gridOnRequestFocusInDescendantsAligned(recyclerView,
+                    direction, previouslyFocusedRect);
+        case BaseGridView.FOCUS_SCROLL_PAGE:
+        case BaseGridView.FOCUS_SCROLL_ITEM:
+            return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
+                    direction, previouslyFocusedRect);
+        }
+    }
+
+    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        View view = getViewByPosition(mFocusPosition);
+        if (view != null) {
+            boolean result = view.requestFocus(direction, previouslyFocusedRect);
+            if (!result && DEBUG) {
+                Log.w(getTag(), "failed to request focus on " + view);
+            }
+            return result;
+        }
+        return false;
+    }
+
+    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        // focus to view not overlapping padding area to avoid scrolling in gaining focus
+        int index;
+        int increment;
+        int end;
+        int count = getChildCount();
+        if ((direction & View.FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        int left = mWindowAlignment.mainAxis().getPaddingLow();
+        int right = mWindowAlignment.mainAxis().getClientSize() + left;
+        for (int i = index; i != end; i += increment) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                    if (child.requestFocus(direction, previouslyFocusedRect)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private final static int PREV_ITEM = 0;
+    private final static int NEXT_ITEM = 1;
+    private final static int PREV_ROW = 2;
+    private final static int NEXT_ROW = 3;
+
+    private int getMovement(int direction) {
+        int movement = View.FOCUS_LEFT;
+
+        if (mOrientation == HORIZONTAL) {
+            switch(direction) {
+                case View.FOCUS_LEFT:
+                    movement = PREV_ITEM;
+                    break;
+                case View.FOCUS_RIGHT:
+                    movement = NEXT_ITEM;
+                    break;
+                case View.FOCUS_UP:
+                    movement = PREV_ROW;
+                    break;
+                case View.FOCUS_DOWN:
+                    movement = NEXT_ROW;
+                    break;
+            }
+         } else if (mOrientation == VERTICAL) {
+             switch(direction) {
+                 case View.FOCUS_LEFT:
+                     movement = PREV_ROW;
+                     break;
+                 case View.FOCUS_RIGHT:
+                     movement = NEXT_ROW;
+                     break;
+                 case View.FOCUS_UP:
+                     movement = PREV_ITEM;
+                     break;
+                 case View.FOCUS_DOWN:
+                     movement = NEXT_ITEM;
+                     break;
+             }
+         }
+
+        return movement;
+    }
+
+    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
+        int focusIndex = getIndexByPosition(mFocusPosition);
+        if (focusIndex == NO_POSITION) {
+            return i;
+        }
+        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
+        // drawing order is 0 1 2 3 9 8 7 6 5 4
+        if (i < focusIndex) {
+            return i;
+        } else if (i < childCount - 1) {
+            return focusIndex + childCount - 1 - i;
+        } else {
+            return focusIndex;
+        }
+    }
+
+    @Override
+    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+            RecyclerView.Adapter newAdapter) {
+        mGrid = null;
+        mRows = null;
+        super.onAdapterChanged(oldAdapter, newAdapter);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
new file mode 100644
index 0000000..a280f4f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * A header item is an item that describes metadata of {@link Row}, such as a category
+ * of media items.  Developer may override this class to add more information.
+ */
+public class HeaderItem {
+
+    private final long mId;
+    private final String mImageUri;
+    private final String mName;
+
+    /**
+     * Create a header item.  All fields are optional.
+     */
+    public HeaderItem(long id, String name, String imageUri) {
+        mId = id;
+        mName = name;
+        mImageUri = imageUri;
+    }
+
+    /**
+     * Create a header item.  All fields are optional.
+     */
+    public HeaderItem(String name, String imageUri) {
+        this(NO_ID, name, imageUri);
+    }
+
+    /**
+     * Returns a unique identifier for this item.
+     */
+    public final long getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the name of this header item.
+     */
+    public final String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the icon for this header item.
+     */
+    public final String getImageUri() {
+        return mImageUri;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
new file mode 100644
index 0000000..2024425
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
@@ -0,0 +1,350 @@
+/*
+ * 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.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view that shows items in a horizontal scrolling list. The items come from
+ * the {@link RecyclerView.Adapter} associated with this view.
+ */
+public class HorizontalGridView extends BaseGridView {
+
+    private boolean mFadingLowEdge;
+    private boolean mFadingHighEdge;
+
+    private Paint mTempPaint = new Paint();
+    private Bitmap mTempBitmapLow;
+    private LinearGradient mLowFadeShader;
+    private int mLowFadeShaderLength;
+    private int mLowFadeShaderOffset;
+    private Bitmap mTempBitmapHigh;
+    private LinearGradient mHighFadeShader;
+    private int mHighFadeShaderLength;
+    private int mHighFadeShaderOffset;
+    private Rect mTempRect = new Rect();
+
+    public HorizontalGridView(Context context) {
+        this(context, null);
+    }
+
+    public HorizontalGridView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HorizontalGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
+        initAttributes(context, attrs);
+    }
+
+    protected void initAttributes(Context context, AttributeSet attrs) {
+        initBaseGridViewAttributes(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView);
+        setRowHeight(a.getDimensionPixelSize(R.styleable.lbHorizontalGridView_rowHeight, 0));
+        setNumRows(a.getInt(R.styleable.lbHorizontalGridView_numberOfRows, 1));
+        a.recycle();
+        setWillNotDraw(false);
+        mTempPaint = new Paint();
+        mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+    }
+
+    /**
+     * Set the number of rows.
+     */
+    public void setNumRows(int numRows) {
+        mLayoutManager.setNumRows(numRows);
+        requestLayout();
+    }
+
+    /**
+     * Set the row height.
+     */
+    public void setRowHeight(int height) {
+        mLayoutManager.setRowHeight(height);
+        requestLayout();
+    }
+
+    /**
+     * Set fade out left edge to transparent.   Note turn on fading edge is very expensive
+     * that you should turn off when HorizontalGridView is scrolling.
+     */
+    public final void setFadingLeftEdge(boolean fading) {
+        if (mFadingLowEdge != fading) {
+            mFadingLowEdge = fading;
+            if (!mFadingLowEdge) {
+                mTempBitmapLow = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Return true if fading left edge.
+     */
+    public final boolean getFadingLeftEdge() {
+        return mFadingLowEdge;
+    }
+
+    /**
+     * Set left edge fading length in pixels.
+     */
+    public final void setFadingLeftEdgeLength(int fadeLength) {
+        if (mLowFadeShaderLength != fadeLength) {
+            mLowFadeShaderLength = fadeLength;
+            if (mLowFadeShaderLength != 0) {
+                mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0,
+                        Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+            } else {
+                mLowFadeShader = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Get left edge fading length in pixels.
+     */
+    public final int getFadingLeftEdgeLength() {
+        return mLowFadeShaderLength;
+    }
+
+    /**
+     * Set distance in pixels between fading start position and left padding edge.
+     * The fading start position is positive when start position is inside left padding
+     * area.  Default value is 0, means that the fading starts from left padding edge.
+     */
+    public final void setFadingLeftEdgeOffset(int fadeOffset) {
+        if (mLowFadeShaderOffset != fadeOffset) {
+            mLowFadeShaderOffset = fadeOffset;
+            invalidate();
+        }
+    }
+
+    /**
+     * Get distance in pixels between fading start position and left padding edge.
+     * The fading start position is positive when start position is inside left padding
+     * area.  Default value is 0, means that the fading starts from left padding edge.
+     */
+    public final int getFadingLeftEdgeOffset() {
+        return mLowFadeShaderOffset;
+    }
+
+    /**
+     * Set fade out right edge to transparent.   Note turn on fading edge is very expensive
+     * that you should turn off when HorizontalGridView is scrolling.
+     */
+    public final void setFadingRightEdge(boolean fading) {
+        if (mFadingHighEdge != fading) {
+            mFadingHighEdge = fading;
+            if (!mFadingHighEdge) {
+                mTempBitmapHigh = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Return true if fading right edge.
+     */
+    public final boolean getFadingRightEdge() {
+        return mFadingHighEdge;
+    }
+
+    /**
+     * Set right edge fading length in pixels.
+     */
+    public final void setFadingRightEdgeLength(int fadeLength) {
+        if (mHighFadeShaderLength != fadeLength) {
+            mHighFadeShaderLength = fadeLength;
+            if (mHighFadeShaderLength != 0) {
+                mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0,
+                        Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
+            } else {
+                mHighFadeShader = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Get right edge fading length in pixels.
+     */
+    public final int getFadingRightEdgeLength() {
+        return mHighFadeShaderLength;
+    }
+
+    /**
+     * Get distance in pixels between fading start position and right padding edge.
+     * The fading start position is positive when start position is inside right padding
+     * area.  Default value is 0, means that the fading starts from right padding edge.
+     */
+    public final void setFadingRightEdgeOffset(int fadeOffset) {
+        if (mHighFadeShaderOffset != fadeOffset) {
+            mHighFadeShaderOffset = fadeOffset;
+            invalidate();
+        }
+    }
+
+    /**
+     * Set distance in pixels between fading start position and right padding edge.
+     * The fading start position is positive when start position is inside right padding
+     * area.  Default value is 0, means that the fading starts from right padding edge.
+     */
+    public final int getFadingRightEdgeOffset() {
+        return mHighFadeShaderOffset;
+    }
+
+    private boolean needsFadingLowEdge() {
+        if (!mFadingLowEdge) {
+            return false;
+        }
+        final int c = getChildCount();
+        for (int i = 0; i < c; i++) {
+            View view = getChildAt(i);
+            if (mLayoutManager.getOpticalLeft(view) <
+                    getPaddingLeft() - mLowFadeShaderOffset) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean needsFadingHighEdge() {
+        if (!mFadingHighEdge) {
+            return false;
+        }
+        final int c = getChildCount();
+        for (int i = c - 1; i >= 0; i--) {
+            View view = getChildAt(i);
+            if (mLayoutManager.getOpticalRight(view) > getWidth()
+                    - getPaddingRight() + mHighFadeShaderOffset) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Bitmap getTempBitmapLow() {
+        if (mTempBitmapLow == null
+                || mTempBitmapLow.getWidth() != mLowFadeShaderLength
+                || mTempBitmapLow.getHeight() != getHeight()) {
+            mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(),
+                    Bitmap.Config.ARGB_8888);
+        }
+        return mTempBitmapLow;
+    }
+
+    private Bitmap getTempBitmapHigh() {
+        if (mTempBitmapHigh == null
+                || mTempBitmapHigh.getWidth() != mHighFadeShaderLength
+                || mTempBitmapHigh.getHeight() != getHeight()) {
+            if (mTempBitmapLow != null
+                    && mTempBitmapLow.getWidth() == mHighFadeShaderLength
+                    && mTempBitmapLow.getHeight() == getHeight()) {
+                // share same bitmap for low edge fading and high edge fading.
+                mTempBitmapHigh = mTempBitmapLow;
+            } else {
+                mTempBitmapLow = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(),
+                        Bitmap.Config.ARGB_8888);
+            }
+        }
+        return mTempBitmapLow;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final boolean needsFadingLow = needsFadingLowEdge();
+        final boolean needsFadingHigh = needsFadingHighEdge();
+        if (!needsFadingLow) {
+            mTempBitmapLow = null;
+        }
+        if (!needsFadingHigh) {
+            mTempBitmapHigh = null;
+        }
+        if (!needsFadingLow && !needsFadingHigh) {
+            super.draw(canvas);
+            return;
+        }
+
+        int lowEdge = mFadingLowEdge? getPaddingLeft() - mLowFadeShaderOffset - mLowFadeShaderLength : 0;
+        int highEdge = mFadingHighEdge ? getWidth() - getPaddingRight()
+                + mHighFadeShaderOffset + mHighFadeShaderLength : getWidth();
+
+        // draw not-fade content
+        int save = canvas.save();
+        canvas.clipRect(lowEdge + mLowFadeShaderLength, 0,
+                highEdge - mHighFadeShaderLength, getHeight());
+        super.draw(canvas);
+        canvas.restoreToCount(save);
+
+        Canvas tmpCanvas = new Canvas();
+        mTempRect.top = 0;
+        mTempRect.bottom = getHeight();
+        if (needsFadingLow && mLowFadeShaderLength > 0) {
+            Bitmap tempBitmap = getTempBitmapLow();
+            tempBitmap.eraseColor(Color.TRANSPARENT);
+            tmpCanvas.setBitmap(tempBitmap);
+            // draw original content
+            int tmpSave = tmpCanvas.save();
+            tmpCanvas.clipRect(0, 0, mLowFadeShaderLength, getHeight());
+            tmpCanvas.translate(-lowEdge, 0);
+            super.draw(tmpCanvas);
+            tmpCanvas.restoreToCount(tmpSave);
+            // draw fading out
+            mTempPaint.setShader(mLowFadeShader);
+            tmpCanvas.drawRect(0, 0, mLowFadeShaderLength, getHeight(), mTempPaint);
+            // copy back to canvas
+            mTempRect.left = 0;
+            mTempRect.right = mLowFadeShaderLength;
+            canvas.translate(lowEdge, 0);
+            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
+            canvas.translate(-lowEdge, 0);
+        }
+        if (needsFadingHigh && mHighFadeShaderLength > 0) {
+            Bitmap tempBitmap = getTempBitmapHigh();
+            tempBitmap.eraseColor(Color.TRANSPARENT);
+            tmpCanvas.setBitmap(tempBitmap);
+            // draw original content
+            int tmpSave = tmpCanvas.save();
+            tmpCanvas.clipRect(0, 0, mHighFadeShaderLength, getHeight());
+            tmpCanvas.translate(-(highEdge - mHighFadeShaderLength), 0);
+            super.draw(tmpCanvas);
+            tmpCanvas.restoreToCount(tmpSave);
+            // draw fading out
+            mTempPaint.setShader(mHighFadeShader);
+            tmpCanvas.drawRect(0, 0, mHighFadeShaderLength, getHeight(), mTempPaint);
+            // copy back to canvas
+            mTempRect.left = 0;
+            mTempRect.right = mHighFadeShaderLength;
+            canvas.translate(highEdge - mHighFadeShaderLength, 0);
+            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
+            canvas.translate(-(highEdge - mHighFadeShaderLength), 0);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
new file mode 100644
index 0000000..0700995
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
@@ -0,0 +1,71 @@
+/*
+ * 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.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.MarginLayoutParams;
+
+/**
+ * Helper class that stay bellow a HorizontalGridView and shows a hover card and align
+ * the hover card left to left of selected child view.  If there is no space when scroll
+ * to the end, right edge hover card will be aligned to right of parent view excluding
+ * right padding.
+ */
+public final class HorizontalHoverCardSwitcher extends PresenterSwitcher {
+    // left and right of selected card view
+    int mCardLeft, mCardRight;
+
+    private int[] mTmpOffsets = new int[2];
+    private Rect mTmpRect = new Rect();
+
+    @Override
+    protected void insertView(View view) {
+        // append hovercard to the end of container
+        getParentViewGroup().addView(view);
+    }
+
+    @Override
+    protected void onViewSelected(View view) {
+        int rightLimit = getParentViewGroup().getWidth() -
+                getParentViewGroup().getPaddingRight();
+        // measure the hover card width, if it's too large,  align hover card
+        // right edge with row view's right edge
+        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+        if (mCardLeft + view.getMeasuredWidth() > rightLimit) {
+            params.leftMargin = rightLimit  - view.getMeasuredWidth();
+        } else {
+            params.leftMargin = mCardLeft;
+        }
+        view.requestLayout();
+    }
+
+    /**
+     * Select a childView inside a grid view and create/bind a corresponding hover card view
+     * for the object.
+     */
+    public void select(HorizontalGridView gridView, View childView, Object object) {
+        ViewGroup parent = getParentViewGroup();
+        gridView.getViewSelectedOffsets(childView, mTmpOffsets);
+        mTmpRect.set(0, 0, childView.getWidth(), childView.getHeight());
+        parent.offsetDescendantRectToMyCoords(childView, mTmpRect);
+        mCardLeft = mTmpRect.left - mTmpOffsets[0];
+        mCardRight = mTmpRect.right - mTmpOffsets[0];
+        select(object);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
new file mode 100644
index 0000000..663a58b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -0,0 +1,163 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class ImageCardView extends BaseCardView {
+
+    private ImageView mImageView;
+    private View mInfoArea;
+    private TextView mTitleView;
+    private TextView mContentView;
+    private ImageView mBadgeImage;
+    private ImageView mBadgeFadeMask;
+
+    public ImageCardView(Context context) {
+        this(context, null);
+    }
+
+    public ImageCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.imageCardViewStyle);
+    }
+
+    public ImageCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View v = inflater.inflate(R.layout.lb_image_card_view, this);
+
+        mImageView = (ImageView) v.findViewById(R.id.main_image);
+        mImageView.setVisibility(View.INVISIBLE);
+        mInfoArea = v.findViewById(R.id.info_field);
+        mTitleView = (TextView) v.findViewById(R.id.title_text);
+        mContentView = (TextView) v.findViewById(R.id.content_text);
+        mBadgeImage = (ImageView) v.findViewById(R.id.extra_badge);
+        mBadgeFadeMask = (ImageView) v.findViewById(R.id.fade_mask);
+    }
+
+    public void setMainImage(Drawable drawable) {
+        if (mImageView == null) {
+            return;
+        }
+
+        mImageView.setImageDrawable(drawable);
+        if (drawable == null) {
+            mImageView.setVisibility(View.INVISIBLE);
+        } else {
+            mImageView.setVisibility(View.VISIBLE);
+            fadeIn(mImageView);
+        }
+    }
+
+    public void setMainImageDimensions(int width, int height) {
+        ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
+        lp.width = width;
+        lp.height = height;
+        mImageView.setLayoutParams(lp);
+    }
+
+    public Drawable getMainImage() {
+        if (mImageView == null) {
+            return null;
+        }
+
+        return mImageView.getDrawable();
+    }
+
+    public void setTitleText(CharSequence text) {
+        if (mTitleView == null) {
+            return;
+        }
+
+        mTitleView.setText(text);
+        setTextMaxLines();
+    }
+
+    public CharSequence getTitleText() {
+        if (mTitleView == null) {
+            return null;
+        }
+
+        return mTitleView.getText();
+    }
+
+    public void setContentText(CharSequence text) {
+        if (mContentView == null) {
+            return;
+        }
+
+        mContentView.setText(text);
+        setTextMaxLines();
+    }
+
+    public CharSequence getContentText() {
+        if (mContentView == null) {
+            return null;
+        }
+
+        return mContentView.getText();
+    }
+
+    public void setBadgeImage(Drawable drawable) {
+        if (mBadgeImage == null) {
+            return;
+        }
+
+        if (drawable != null) {
+            mBadgeImage.setImageDrawable(drawable);
+            mBadgeImage.setVisibility(View.VISIBLE);
+            mBadgeFadeMask.setVisibility(View.VISIBLE);
+        } else {
+            mBadgeImage.setVisibility(View.GONE);
+            mBadgeFadeMask.setVisibility(View.GONE);
+        }
+    }
+
+    public Drawable getBadgeImage() {
+        if (mBadgeImage == null) {
+            return null;
+        }
+
+        return mBadgeImage.getDrawable();
+    }
+
+    private void fadeIn(View v) {
+        v.setAlpha(0f);
+        v.animate().alpha(1f).setDuration(v.getContext().getResources().getInteger(
+                android.R.integer.config_shortAnimTime)).start();
+    }
+
+    private void setTextMaxLines() {
+        if (TextUtils.isEmpty(getTitleText())) {
+            mContentView.setMaxLines(2);
+        } else {
+            mContentView.setMaxLines(1);
+        }
+        if (TextUtils.isEmpty(getContentText())) {
+            mTitleView.setMaxLines(2);
+        } else {
+            mTitleView.setMaxLines(1);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
new file mode 100644
index 0000000..cefb431
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+import static android.support.v17.leanback.widget.BaseGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
+
+import android.graphics.Rect;
+import android.support.v17.leanback.widget.GridLayoutManager.LayoutParams;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Defines alignment position on two directions of an item view. Typically item
+ * view alignment is at the center of the view. The class allows defining
+ * alignment at left/right or fixed offset/percentage position; it also allows
+ * using descendant view by id match.
+ */
+class ItemAlignment {
+
+    final static class Axis {
+        private int mOrientation;
+        private int mOffset = 0;
+        private float mOffsetPercent = 50;
+        private int mViewId = 0;
+        private Rect mRect = new Rect();
+
+        Axis(int orientation) {
+            mOrientation = orientation;
+        }
+
+        public void setItemAlignmentOffset(int offset) {
+            mOffset = offset;
+        }
+
+        public int getItemAlignmentOffset() {
+            return mOffset;
+        }
+
+        public void setItemAlignmentOffsetPercent(float percent) {
+            if ( (percent < 0 || percent > 100) &&
+                    percent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                throw new IllegalArgumentException();
+            }
+            mOffsetPercent = percent;
+        }
+
+        public float getItemAlignmentOffsetPercent() {
+            return mOffsetPercent;
+        }
+
+        public void setItemAlignmentViewId(int viewId) {
+            mViewId = viewId;
+        }
+
+        public int getItemAlignmentViewId() {
+            return mViewId;
+        }
+
+        public int getAlignmentPosition(View itemView) {
+
+            LayoutParams p = (LayoutParams) itemView.getLayoutParams();
+            View view = itemView;
+            if (mViewId != 0) {
+                view = itemView.findViewById(mViewId);
+                if (view == null) {
+                    view = itemView;
+                }
+            }
+            int alignPos;
+            if (mOrientation == HORIZONTAL) {
+                if (mOffset >= 0) {
+                    alignPos = mOffset;
+                } else {
+                    alignPos = p.getOpticalWidth(itemView) + mOffset;
+                }
+                if (mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    alignPos += (p.getOpticalWidth(itemView) * mOffsetPercent) / 100;
+                }
+                if (itemView != view) {
+                    mRect.left = alignPos;
+                    ((ViewGroup) itemView).offsetDescendantRectToMyCoords(view, mRect);
+                    alignPos = mRect.left;
+                }
+            } else {
+                if (mOffset >= 0) {
+                    alignPos = mOffset;
+                } else {
+                    alignPos = p.getOpticalHeight(itemView) + mOffset;
+                }
+                if (mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    alignPos += (p.getOpticalHeight(itemView) * mOffsetPercent) / 100;
+                }
+                if (itemView != view) {
+                    mRect.top = alignPos;
+                    ((ViewGroup) itemView).offsetDescendantRectToMyCoords(view, mRect);
+                    alignPos = mRect.top;
+                }
+            }
+            return alignPos;
+        }
+    }
+
+    private int mOrientation = HORIZONTAL;
+
+    final public Axis vertical = new Axis(VERTICAL);
+
+    final public Axis horizontal = new Axis(HORIZONTAL);
+
+    private Axis mMainAxis = horizontal;
+
+    private Axis mSecondAxis = vertical;
+
+    final public Axis mainAxis() {
+        return mMainAxis;
+    }
+
+    final public Axis secondAxis() {
+        return mSecondAxis;
+    }
+
+    final public void setOrientation(int orientation) {
+        mOrientation = orientation;
+        if (mOrientation == HORIZONTAL) {
+            mMainAxis = horizontal;
+            mSecondAxis = vertical;
+        } else {
+            mMainAxis = vertical;
+            mSecondAxis = horizontal;
+        }
+    }
+
+    final public int getOrientation() {
+        return mOrientation;
+    }
+
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
new file mode 100644
index 0000000..bc4a476
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
@@ -0,0 +1,306 @@
+/*
+ * 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.v7.widget.RecyclerView;
+import android.support.v17.leanback.R;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Bridge from Presenter to RecyclerView.Adapter. Public to allow use by third
+ * party presenters.
+ */
+public class ItemBridgeAdapter extends RecyclerView.Adapter {
+    private static final String TAG = "ItemBridgeAdapter";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Interface for listening to view holder operations.
+     */
+    public static class AdapterListener {
+        public void onAddPresenter(Presenter presenter) {
+        }
+        public void onCreate(ViewHolder viewHolder) {
+        }
+        public void onBind(ViewHolder viewHolder) {
+        }
+        public void onUnbind(ViewHolder viewHolder) {
+        }
+        public void onAttachedToWindow(ViewHolder viewHolder) {
+        }
+        public void onDetachedFromWindow(ViewHolder viewHolder) {
+        }
+    }
+
+    /**
+     * Interface for wrapping a view created by presenter into another view.
+     * The wrapper must be immediate parent of the wrapped view.
+     */
+    public static abstract class Wrapper {
+        public abstract View createWrapper(View root);
+        public abstract void wrap(View wrapper, View wrapped);
+    }
+
+    private ObjectAdapter mAdapter;
+    private Wrapper mWrapper;
+    private PresenterSelector mPresenterSelector;
+    private FocusHighlight mFocusHighlight;
+    private AdapterListener mAdapterListener;
+    private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
+
+    final class OnFocusChangeListener implements View.OnFocusChangeListener {
+        View.OnFocusChangeListener mChainedListener;
+
+        @Override
+        public void onFocusChange(View view, boolean hasFocus) {
+            if (DEBUG) Log.v(TAG, "onFocusChange " + hasFocus + " " + view
+                    + " mFocusHighlight" + mFocusHighlight);
+            if (mWrapper != null) {
+                view = (View) view.getParent();
+            }
+            if (mFocusHighlight != null) {
+                mFocusHighlight.onItemFocused(view, hasFocus);
+            }
+            if (mChainedListener != null) {
+                mChainedListener.onFocusChange(view, hasFocus);
+            }
+        }
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+        final Presenter mPresenter;
+        final Presenter.ViewHolder mHolder;
+        final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
+        Object mItem;
+        Object mExtraObject;
+
+        /**
+         * Get {@link Presenter}.
+         */
+        public final Presenter getPresenter() {
+            return mPresenter;
+        }
+
+        /**
+         * Get {@link Presenter.ViewHolder}.
+         */
+        public final Presenter.ViewHolder getViewHolder() {
+            return mHolder;
+        }
+
+        /**
+         * Get currently bound object.
+         */
+        public final Object getItem() {
+            return mItem;
+        }
+
+        /**
+         * Get extra object associated with the view.  Developer can attach
+         * any customized UI object in addition to {@link Presenter.ViewHolder}.
+         * A typical use case is attaching an animator object.
+         */
+        public final Object getExtraObject() {
+            return mExtraObject;
+        }
+
+        /**
+         * Set extra object associated with the view.  Developer can attach
+         * any customized UI object in addition to {@link Presenter.ViewHolder}.
+         * A typical use case is attaching an animator object.
+         */
+        public void setExtraObject(Object object) {
+            mExtraObject = object;
+        }
+
+        ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
+            super(view);
+            mPresenter = presenter;
+            mHolder = holder;
+        }
+    }
+
+    private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
+        @Override
+        public void onChanged() {
+            ItemBridgeAdapter.this.notifyDataSetChanged();
+        }
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
+        }
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
+        }
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
+        }
+    };
+
+    public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
+        setAdapter(adapter);
+        mPresenterSelector = presenterSelector;
+    }
+
+    public ItemBridgeAdapter(ObjectAdapter adapter) {
+        this(adapter, null);
+    }
+
+    public ItemBridgeAdapter() {
+    }
+
+    public void setAdapter(ObjectAdapter adapter) {
+        if (mAdapter != null) {
+            mAdapter.unregisterObserver(mDataObserver);
+        }
+        mAdapter = adapter;
+        if (mAdapter == null) {
+            return;
+        }
+
+        mAdapter.registerObserver(mDataObserver);
+        if (hasStableIds() != mAdapter.hasStableIds()) {
+            setHasStableIds(mAdapter.hasStableIds());
+        }
+    }
+
+    public void setWrapper(Wrapper wrapper) {
+        mWrapper = wrapper;
+    }
+
+    public Wrapper getWrapper() {
+        return mWrapper;
+    }
+
+    void setFocusHighlight(FocusHighlight listener) {
+        mFocusHighlight = listener;
+        if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
+    }
+
+    public void clear() {
+        setAdapter(null);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mAdapter.size();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        PresenterSelector presenterSelector = mPresenterSelector != null ?
+                mPresenterSelector : mAdapter.getPresenterSelector();
+        Object item = mAdapter.get(position);
+        Presenter presenter = presenterSelector.getPresenter(item);
+        int type = mPresenters.indexOf(presenter);
+        if (type < 0) {
+            mPresenters.add(presenter);
+            type = mPresenters.indexOf(presenter);
+            if (mAdapterListener != null) {
+                mAdapterListener.onAddPresenter(presenter);
+            }
+        }
+        return type;
+    }
+
+    /**
+     * {@link View.OnFocusChangeListener} that assigned in
+     * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
+     * {@link View.OnFocusChangeListener} after that.
+     */
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
+        Presenter presenter = mPresenters.get(viewType);
+        Presenter.ViewHolder presenterVh;
+        View view;
+        if (mWrapper != null) {
+            view = mWrapper.createWrapper(parent);
+            presenterVh = presenter.onCreateViewHolder(parent);
+            mWrapper.wrap(view, presenterVh.view);
+        } else {
+            presenterVh = presenter.onCreateViewHolder(parent);
+            view = presenterVh.view;
+        }
+        ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
+        if (mAdapterListener != null) {
+            mAdapterListener.onCreate(viewHolder);
+        }
+        View presenterView = viewHolder.mHolder.view;
+        if (presenterView != null) {
+            viewHolder.mFocusChangeListener.mChainedListener = presenterView.getOnFocusChangeListener();
+            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
+        }
+        return viewHolder;
+    }
+
+    public void setAdapterListener(AdapterListener listener) {
+        mAdapterListener = listener;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mItem = mAdapter.get(position);
+
+        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
+
+        if (mAdapterListener != null) {
+            mAdapterListener.onBind(viewHolder);
+        }
+    }
+
+    @Override
+    public void onViewRecycled(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
+
+        viewHolder.mItem = null;
+
+        if (mAdapterListener != null) {
+            mAdapterListener.onUnbind(viewHolder);
+        }
+    }
+
+    @Override
+    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        if (mAdapterListener != null) {
+            mAdapterListener.onAttachedToWindow(viewHolder);
+        }
+        viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
+        if (mAdapterListener != null) {
+            mAdapterListener.onDetachedFromWindow(viewHolder);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mAdapter.getId(position);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
new file mode 100644
index 0000000..3ce18f7
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * A row composed of a optional {@link HeaderItem}, and an {@link ObjectAdapter}
+ * describing children.
+ */
+public class ListRow extends Row {
+    private final ObjectAdapter mAdapter;
+
+    /**
+     * Get the {@link ObjectAdapter} that represents a list of objects.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    public ListRow(HeaderItem header, ObjectAdapter adapter) {
+        super(header);
+        mAdapter = adapter;
+        verify();
+    }
+
+    public ListRow(long id, HeaderItem header, ObjectAdapter adapter) {
+        super(id, header);
+        mAdapter = adapter;
+        verify();
+    }
+
+    public ListRow(ObjectAdapter adapter) {
+        super();
+        mAdapter = adapter;
+        verify();
+    }
+
+    private void verify() {
+        if (mAdapter == null) {
+            throw new IllegalArgumentException("ObjectAdapter cannot be null");
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
new file mode 100644
index 0000000..c5b2f49
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
@@ -0,0 +1,74 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * ListRowHoverCardView contains a title and description.
+ */
+public final class ListRowHoverCardView extends LinearLayout {
+
+    private final TextView mTitleView;
+    private final TextView mDescriptionView;
+
+    public ListRowHoverCardView(Context context) {
+       this(context, null);
+    }
+
+    public ListRowHoverCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ListRowHoverCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_list_row_hovercard, this);
+        mTitleView = (TextView) findViewById(R.id.title);
+        mDescriptionView = (TextView) findViewById(R.id.description);
+    }
+
+    public final CharSequence getTitle() {
+        return mTitleView.getText();
+    }
+
+    public final void setTitle(CharSequence text) {
+        if (!TextUtils.isEmpty(text)) {
+            mTitleView.setText(text);
+            mTitleView.setVisibility(View.VISIBLE);
+        } else {
+            mTitleView.setVisibility(View.GONE);
+        }
+    }
+
+    public final CharSequence getDescription() {
+        return mDescriptionView.getText();
+    }
+
+    public final void setDescription(CharSequence text) {
+        if (!TextUtils.isEmpty(text)) {
+            mDescriptionView.setText(text);
+            mDescriptionView.setVisibility(View.VISIBLE);
+        } else {
+            mDescriptionView.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
new file mode 100644
index 0000000..0de39ab
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -0,0 +1,377 @@
+/*
+ * 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 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.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+/**
+ * ListRowPresenter renders {@link ListRow} using a
+ * {@link HorizontalGridView} hosted in a {@link ListRowView}.
+ *
+ * <h3>Hover card</h3>
+ * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
+ * display a view for the currently focused list item below the rendered
+ * list. This view is known as a hover card.
+ *
+ * <h3>Selection animation</h3>
+ * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draw
+ * a dim overlay on top of each individual child items.  Subclass may override and disable
+ * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
+ * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
+ *
+ * <h3>Shadow</h3>
+ * ListRowPresenter applies a default shadow to child of each view.  Call
+ * {@link #setShadowEnabled(boolean)} to disable shadow.  Subclass may override and return
+ * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation.
+ */
+public class ListRowPresenter extends RowPresenter {
+
+    private static final String TAG = "ListRowPresenter";
+    private static final boolean DEBUG = false;
+
+    public static class ViewHolder extends RowPresenter.ViewHolder {
+        final ListRowPresenter mListRowPresenter;
+        final HorizontalGridView mGridView;
+        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
+        final ColorOverlayDimmer mColorDimmer;
+
+        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
+            super(rootView);
+            mGridView = gridView;
+            mListRowPresenter = p;
+            mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext());
+        }
+
+        public final ListRowPresenter getListRowPresenter() {
+            return mListRowPresenter;
+        }
+
+        public final HorizontalGridView getGridView() {
+            return mGridView;
+        }
+    }
+
+    private PresenterSelector mHoverCardPresenterSelector;
+    private int mZoomFactor;
+    private boolean mShadowEnabled = true;
+    private int mBrowseRowsFadingEdgeLength = -1;
+
+    /**
+     * Constructs a ListRowPresenter with defaults.
+     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming.
+     */
+    public ListRowPresenter() {
+        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
+    }
+
+    /**
+     * Constructs a ListRowPresenter with the given parameters.
+     *
+     * @param zoomFactor Controls the zoom factor used when an item view is focused. One of
+     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
+     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
+     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
+     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
+     */
+    public ListRowPresenter(int zoomFactor) {
+        mZoomFactor = zoomFactor;
+    }
+
+    /**
+     * Returns the zoom factor used for focus highlighting.
+     */
+    public final int getZoomFactor() {
+        return mZoomFactor;
+    }
+
+    private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public View createWrapper(View root) {
+            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
+            wrapper.setLayoutParams(
+                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            wrapper.initialize(needsDefaultShadow(), needsDefaultListSelectEffect());
+            return wrapper;
+        }
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+        }
+    };
+
+    @Override
+    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
+        super.initializeRowViewHolder(holder);
+        final ViewHolder rowViewHolder = (ViewHolder) holder;
+        if (needsDefaultListSelectEffect() || needsDefaultShadow()) {
+            rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
+        }
+        if (needsDefaultListSelectEffect()) {
+            ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
+            ((ViewGroup) rowViewHolder.view).setClipChildren(false);
+            if (rowViewHolder.mContainerViewHolder != null) {
+                ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false);
+            }
+        }
+        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor);
+        rowViewHolder.mGridView.setOnChildSelectedListener(
+                new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectChildView(rowViewHolder, view);
+            }
+        });
+        rowViewHolder.mItemBridgeAdapter.setAdapterListener(
+                new ItemBridgeAdapter.AdapterListener() {
+            @Override
+            public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
+                // Only when having an OnItemClickListner, we will attach the OnClickListener.
+                if (getOnItemClickedListener() != null) {
+                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
+                                    rowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
+                            if (getOnItemClickedListener() != null) {
+                                getOnItemClickedListener().onItemClicked(ibh.mItem,
+                                        (ListRow) rowViewHolder.mRow);
+                            }
+                        }
+                    });
+                }
+            }
+
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                if (viewHolder.itemView instanceof ShadowOverlayContainer) {
+                    int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor();
+                    ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
+                }
+            }
+        });
+    }
+
+    final boolean needsDefaultListSelectEffect() {
+        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
+    }
+
+    /**
+     * Set {@link PresenterSelector} used for showing a select object in a hover card.
+     */
+    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
+        mHoverCardPresenterSelector = selector;
+    }
+
+    /**
+     * Get {@link PresenterSelector} used for showing a select object in a hover card.
+     */
+    public final PresenterSelector getHoverCardPresenterSelector() {
+        return mHoverCardPresenterSelector;
+    }
+
+    /*
+     * Perform operations when a child of horizontal grid view is selected.
+     */
+    private void selectChildView(ViewHolder rowViewHolder, View view) {
+        ItemBridgeAdapter.ViewHolder ibh = null;
+        if (view != null) {
+            ibh = (ItemBridgeAdapter.ViewHolder)
+                    rowViewHolder.mGridView.getChildViewHolder(view);
+        }
+        if (view == null) {
+            if (mHoverCardPresenterSelector != null) {
+                rowViewHolder.mHoverCardViewSwitcher.unselect();
+            }
+            if (getOnItemSelectedListener() != null) {
+                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
+            }
+        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
+            if (mHoverCardPresenterSelector != null) {
+                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
+                        ibh.mItem);
+            }
+            if (getOnItemSelectedListener() != null) {
+                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
+            }
+        }
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        ListRowView rowView = new ListRowView(parent.getContext());
+        setupFadingEffect(rowView);
+        return new ViewHolder(rowView, rowView.getGridView(), this);
+    }
+
+    @Override
+    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
+        updateFooterViewSwitcher((ViewHolder) holder);
+    }
+
+    /*
+     * Show or hide hover card when row selection or expanded state is changed.
+     */
+    private void updateFooterViewSwitcher(ViewHolder vh) {
+        if (vh.mExpanded && vh.mSelected) {
+            if (mHoverCardPresenterSelector != null) {
+                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
+                        mHoverCardPresenterSelector);
+            }
+            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
+                    vh.mGridView.findViewHolderForPosition(
+                            vh.mGridView.getSelectedPosition());
+            selectChildView(vh, ibh == null ? null : ibh.itemView);
+        } else {
+            if (mHoverCardPresenterSelector != null) {
+                vh.mHoverCardViewSwitcher.clear();
+            }
+        }
+    }
+
+    private void setupFadingEffect(ListRowView rowView) {
+        // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding.
+        HorizontalGridView gridView = rowView.getGridView();
+        if (mBrowseRowsFadingEdgeLength < 0) {
+            TypedArray ta = gridView.getContext()
+                    .obtainStyledAttributes(R.styleable.LeanbackTheme);
+            mBrowseRowsFadingEdgeLength = (int) ta.getDimension(
+                    R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0);
+            ta.recycle();
+        }
+        gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength);
+    }
+
+    @Override
+    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
+        super.onRowViewExpanded(holder, expanded);
+        ViewHolder vh = (ViewHolder) holder;
+        vh.getGridView().setFadingLeftEdge(!expanded);
+        updateFooterViewSwitcher(vh);
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
+        ViewHolder vh = (ViewHolder) holder;
+        ListRow rowItem = (ListRow) item;
+        vh.mItemBridgeAdapter.clear();
+        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
+        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        ((ViewHolder) holder).mGridView.setAdapter(null);
+        super.onUnbindRowViewHolder(holder);
+    }
+
+    /**
+     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
+     * and return false.
+     */
+    @Override
+    public final boolean isUsingDefaultSelectEffect() {
+        return false;
+    }
+
+    /**
+     * Returns true so that default select effect is applied to each individual
+     * child of {@link HorizontalGridView}.  Subclass may return false to disable
+     * the default implementation.
+     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
+     */
+    public boolean isUsingDefaultListSelectEffect() {
+        return true;
+    }
+
+    /**
+     * Returns true if SDK >= 18, where default shadow
+     * is applied to each individual child of {@link HorizontalGridView}.
+     * Subclass may return false to disable.
+     */
+    public boolean isUsingDefaultShadow() {
+        return ShadowOverlayContainer.supportsShadow();
+    }
+
+    /**
+     * Enable or disable child shadow.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final void setShadowEnabled(boolean enabled) {
+        mShadowEnabled = enabled;
+    }
+
+    /**
+     * Returns true if child shadow is enabled.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final boolean getShadowEnabled() {
+        return mShadowEnabled;
+    }
+
+    final boolean needsDefaultShadow() {
+        return isUsingDefaultShadow() && getShadowEnabled();
+    }
+
+    @Override
+    public boolean canDrawOutOfBounds() {
+        return needsDefaultShadow();
+    }
+
+    /**
+     * Applies select level to header and draw a default color dim over each child
+     * of {@link HorizontalGridView}.
+     * <p>
+     * Subclass may override this method.  A subclass
+     * needs to call super.onSelectLevelChanged() for applying header select level
+     * and optionally applying a default select level to each child view of
+     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
+     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
+     * false and deal with the individual item select level by itself.
+     * </p>
+     */
+    @Override
+    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
+        super.onSelectLevelChanged(holder);
+        if (needsDefaultListSelectEffect()) {
+            ViewHolder vh = (ViewHolder) holder;
+            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
+            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
+            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
+                ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i);
+                wrapper.setOverlayColor(dimmedColor);
+            }
+            if (vh.mGridView.getFadingLeftEdge()) {
+                vh.mGridView.invalidate();
+            }
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
new file mode 100644
index 0000000..41da46f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
@@ -0,0 +1,62 @@
+/*
+ * 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.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.
+ */
+public final class ListRowView extends LinearLayout {
+
+    private HorizontalGridView mGridView;
+
+    public ListRowView(Context context) {
+        this(context, null);
+    }
+
+    public ListRowView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ListRowView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_list_row, this);
+
+        mGridView = (HorizontalGridView) findViewById(R.id.row_content);
+        // Uncomment this to experiment with page-based scrolling.
+        // mGridView.setFocusScrollStrategy(HorizontalGridView.FOCUS_SCROLL_PAGE);
+
+        setOrientation(LinearLayout.VERTICAL);
+        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+    }
+
+    public HorizontalGridView getGridView() {
+        return mGridView;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
new file mode 100644
index 0000000..011b9c6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.database.Observable;
+
+/**
+ * Adapter for leanback activities.  Provides access to a data model and is
+ * decoupled from the presentation of the items via {@link PresenterSelector}.
+ */
+public abstract class ObjectAdapter {
+
+    public static final int NO_ID = -1;
+
+    /**
+     * A DataObserver can be notified when an ObjectAdapter's underlying data
+     * changes. Separate methods provide notifications about different types of
+     * changes.
+     */
+    public static abstract class DataObserver {
+        /**
+         * Called whenever the ObjectAdapter's data has changed in some manner
+         * outside of the set of changes covered by the other range based change
+         * notification methods.
+         */
+        public void onChanged() {
+        }
+
+        /**
+         * Called when a range of items in the ObjectAdapter has changed. The
+         * basic ordering and structure of the ObjectAdapter has not changed.
+         *
+         * @param positionStart The position of the first item that changed.
+         * @param itemCount The number of items changed.
+         */
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is inserted into the ObjectAdapter.
+         *
+         * @param positionStart The position of the first inserted item.
+         * @param itemCount The number of items inserted.
+         */
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is removed from the ObjectAdapter.
+         *
+         * @param positionStart The position of the first removed item.
+         * @param itemCount The number of items removed.
+         */
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            onChanged();
+        }
+    }
+
+    private static final class DataObservable extends Observable<DataObserver> {
+
+        public void notifyChanged() {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeInserted(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+            }
+        }
+    }
+
+    private final DataObservable mObservable = new DataObservable();
+    private boolean mHasStableIds;
+    private PresenterSelector mPresenterSelector;
+
+    /**
+     * Construct an adapter with the given {@link PresenterSelector}.
+     */
+    public ObjectAdapter(PresenterSelector presenterSelector) {
+        setPresenterSelector(presenterSelector);
+    }
+
+    /**
+     * Construct an adapter that uses the given {@link Presenter} for all items.
+     */
+    public ObjectAdapter(Presenter presenter) {
+        setPresenterSelector(new SinglePresenterSelector(presenter));
+    }
+
+    /**
+     * Construct an adapter.
+     */
+    public ObjectAdapter() {
+    }
+
+    /**
+     * Set the presenter selector.  May not be null.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        if (presenterSelector == null) {
+            throw new IllegalArgumentException("Presenter selector must not be null");
+        }
+        final boolean update = (mPresenterSelector != null);
+        final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
+
+        mPresenterSelector = presenterSelector;
+
+        if (selectorChanged) {
+            onPresenterSelectorChanged();
+        }
+        if (update) {
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setPresenterSelector(PresenterSelector)} is called
+     * and the PresenterSelector differs from the previous one.
+     */
+    protected void onPresenterSelectorChanged() {
+    }
+
+    /**
+     * Returns the presenter selector;
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Register a DataObserver for data change notifications.
+     */
+    public final void registerObserver(DataObserver observer) {
+        mObservable.registerObserver(observer);
+    }
+
+    /**
+     * Unregister a DataObserver for data change notifications.
+     */
+    public final void unregisterObserver(DataObserver observer) {
+        mObservable.unregisterObserver(observer);
+    }
+
+    /**
+     * Unregister all DataObservers for this ObservableList.
+     */
+    public final void unregisterAllObservers() {
+        mObservable.unregisterAll();
+    }
+
+    final protected void notifyItemRangeChanged(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeInserted(positionStart, itemCount);
+    }
+
+    final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+    }
+
+    final protected void notifyChanged() {
+        mObservable.notifyChanged();
+    }
+
+    /**
+     * Indicates whether the item ids are stable across changes to the
+     * underlying data.  When this is true, client of Adapter can use
+     * {@link #getId(int)} to correlate objects across changes.
+     */
+    public final boolean hasStableIds() {
+        return mHasStableIds;
+    }
+
+    /**
+     * Sets whether the item ids are stable across changes to the underlying
+     * data.
+     */
+    public final void setHasStableIds(boolean hasStableIds) {
+        boolean changed = mHasStableIds != hasStableIds;
+        mHasStableIds = hasStableIds;
+
+        if (changed) {
+            onHasStableIdsChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setHasStableIds(boolean)} is called and the status
+     * of stable ids has changed.
+     */
+    protected void onHasStableIdsChanged() {
+    }
+
+    /**
+     * Returns the {@link Presenter} for the given item from the adapter.
+     */
+    public final Presenter getPresenter(Object item) {
+        if (mPresenterSelector == null) {
+            throw new IllegalStateException("Presenter selector must not be null");
+        }
+        return mPresenterSelector.getPresenter(item);
+    }
+
+    /**
+     * Returns the number of items in the adapter.
+     */
+    public abstract int size();
+
+    /**
+     * Returns the item for the given position.
+     */
+    public abstract Object get(int position);
+
+    /**
+     * Returns id for the given position.
+     */
+    public long getId(int position) {
+        return NO_ID;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
new file mode 100644
index 0000000..531c1cf
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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.view.View;
+
+/**
+ * Interface for receiving notification when an action is clicked.
+ */
+public interface OnActionClickedListener {
+
+    public void onActionClicked(Action action);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
new file mode 100644
index 0000000..f5f18f8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Interface definition for a callback to be invoked when a child of this
+ * viewgroup has been selected.
+ */
+public interface OnChildSelectedListener {
+    /**
+     * Callback method to be invoked when a child of this viewgroup has been
+     * selected.
+     *
+     * <p>This method may be called during layout, so implementations of this
+     * interface need to be careful not to ... (todo).
+     *
+     * @param parent The ViewGroup where the selection happened.
+     * @param view The view within the ViewGroup that is selected, or null if no
+     *        view is selected.
+     * @param position The position of the view in the adapter, or NO_POSITION
+     *        if no view is selected.
+     * @param id The id of the child that is selected, or NO_ID if no view is
+     *        selected.
+     */
+    void onChildSelected(ViewGroup parent, View view, int position, long id);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java
new file mode 100644
index 0000000..9530f90
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java
@@ -0,0 +1,30 @@
+/*
+ * 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.view.View;
+
+/**
+ * Interface for receiving notification when a item is clicked.
+ * <p>
+ * Alternatively {@link Presenter} can attach its own {@link View.OnClickListener} in
+ * {@link Presenter#onCreateViewHolder(android.view.ViewGroup)}; but developer should never
+ * use these two listeners together.
+ * </p>
+ */
+public interface OnItemClickedListener {
+
+    public void onItemClicked(Object item, Row row);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java
new file mode 100644
index 0000000..946c69d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * Interface for receiving notification when a row or item becomes selected.
+ */
+public interface OnItemSelectedListener {
+    /**
+     * Called when the a row or a new item becomes selected.  The concept of current selection
+     * is different than focus.  Row or item can be selected even they don't have focus.
+     * Having the concept of selection will allow developer to switch background to selected
+     * item or selected row when user selects rows outside row UI (e.g. a fast lane next to
+     * rows).
+     * <p>
+     * For a none {@link ListRow} case,  parameter item is always null.  Event is fired when
+     * selection changes between rows, regardless if row view has focus or not.
+     * <p>
+     * For a {@link ListRow} case, parameter item can be null if the list row is empty.
+     * </p>
+     * <p>
+     * In the case of a grid, the row parameter is always null.
+     * </p>
+     * <li>
+     * Row has focus: event is fired when focus changes between child of the row.
+     * </li>
+     * <li>
+     * None of the row has focus: the event is fired with the current selected row and last
+     * focused item in the row.
+     * </li>
+     *
+     * @param item The item that is currently selected.
+     * @param row The row that is currently selected.
+     */
+    public void onItemSelected(Object item, Row row);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
new file mode 100644
index 0000000..deffa45
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A Presenter is used to generate {@link View}s and bind Objects to them on
+ * demand. It is closely related to concept of an {@link
+ * android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, but is
+ * not position-based.
+ *
+ * <p>
+ * A trivial Presenter that takes a string and renders it into a {@link
+ * android.widget.TextView TextView}:
+ *
+ * <pre class="prettyprint">
+ * public class StringTextViewPresenter extends Presenter {
+ *     // This class does not need a custom ViewHolder, since it does not use
+ *     // a complex layout.
+ *
+ *     {@literal @}Override
+ *     public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ *         return new ViewHolder(new TextView(parent.getContext()));
+ *     }
+ *
+ *     {@literal @}Override
+ *     public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+ *         String str = (String) item;
+ *         TextView textView = (TextView) viewHolder.mView;
+ *
+ *         textView.setText(item);
+ *     }
+ *
+ *     {@literal @}Override
+ *     public void onUnbindViewHolder(ViewHolder viewHolder) {
+ *         // Nothing to unbind for TextView, but if this viewHolder had
+ *         // allocated bitmaps, they can be released here.
+ *     }
+ * }
+ * </pre>
+ */
+public abstract class Presenter {
+    /**
+     * ViewHolder can be subclassed and used to cache any view accessors needed
+     * to improve binding performance (for example, results of findViewById)
+     * without needing to subclass a View.
+     */
+    public static class ViewHolder {
+        public final View view;
+
+        public ViewHolder(View view) {
+            this.view = view;
+        }
+    }
+
+    /**
+     * Creates a new {@link View}.
+     */
+    public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
+
+    /**
+     * Binds a {@link View} to an item.
+     */
+    public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
+
+    /**
+     * Unbinds a {@link View} from an item. Any expensive references may be
+     * released here, and any fields that are not bound for every item should be
+     * cleared here.
+     */
+    public abstract void onUnbindViewHolder(ViewHolder viewHolder);
+
+    /**
+     * Called when a view created by this presenter has been attached to a window.
+     *
+     * <p>This can be used as a reasonable signal that the view is about to be seen
+     * by the user. If the adapter previously freed any resources in
+     * {@link #onViewDetachedFromWindow(ViewHolder)}
+     * those resources should be restored here.</p>
+     *
+     * @param holder Holder of the view being attached
+     */
+    public void onViewAttachedToWindow(ViewHolder holder) {
+    }
+
+    /**
+     * Called when a view created by this presenter has been detached from its window.
+     *
+     * <p>Becoming detached from the window is not necessarily a permanent condition;
+     * the consumer of an presenter's views may choose to cache views offscreen while they
+     * are not visible, attaching an detaching them as appropriate.</p>
+     *
+     * @param holder Holder of the view being detached
+     */
+    public void onViewDetachedFromWindow(ViewHolder holder) {
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
new file mode 100644
index 0000000..c38957d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * A PresenterSelector is used to obtain a {@link Presenter} for a given Object.
+ */
+public abstract class PresenterSelector {
+    /**
+     * Returns a presenter for the given item.
+     */
+    public abstract Presenter getPresenter(Object item);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
new file mode 100644
index 0000000..8a9c726
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
@@ -0,0 +1,106 @@
+/*
+ * 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.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An abstract helper class that switches view in parent view using {@link PresenterSelector}
+ * subclass should define {@link #insertView(View)} of how to add the view
+ * in parent and optionally override {@link #onViewSelected(View)}.
+ */
+public abstract class PresenterSwitcher {
+
+    private ViewGroup mParent;
+    private PresenterSelector mPresenterSelector;
+    private Presenter mCurrentPresenter;
+    private Presenter.ViewHolder mCurrentViewHolder;
+
+    /**
+     * Initialize switcher with a parent view to insert view into and a
+     * {@link PresenterSelector} for choose {@link Presenter} for object.
+     * This will destroy any existing views.
+     */
+    public void init(ViewGroup parent, PresenterSelector presenterSelector) {
+        clear();
+        mParent = parent;
+        mPresenterSelector = presenterSelector;
+    }
+
+    public void select(Object object) {
+        switchView(object);
+        showView(true);
+    }
+
+    public void unselect() {
+        showView(false);
+    }
+
+    public final ViewGroup getParentViewGroup() {
+        return mParent;
+    }
+
+    private void showView(boolean show) {
+        if (mCurrentViewHolder != null) {
+            showView(mCurrentViewHolder.view, show);
+        }
+    }
+
+    private void switchView(Object object) {
+        Presenter presenter = mPresenterSelector.getPresenter(object);
+        if (presenter != mCurrentPresenter) {
+            showView(false);
+            clear();
+            mCurrentPresenter = presenter;
+            if (mCurrentPresenter == null) {
+                return;
+            }
+            mCurrentViewHolder = mCurrentPresenter.onCreateViewHolder(mParent);
+            insertView(mCurrentViewHolder.view);
+        } else {
+            if (mCurrentPresenter == null) {
+                return;
+            }
+            mCurrentPresenter.onUnbindViewHolder(mCurrentViewHolder);
+        }
+        mCurrentPresenter.onBindViewHolder(mCurrentViewHolder, object);
+        onViewSelected(mCurrentViewHolder.view);
+    }
+
+    protected abstract void insertView(View view);
+
+    /**
+     * Called when a view is bound to the object of {@link #select(Object)}.
+     */
+    protected void onViewSelected(View view) {
+    }
+
+    protected void showView(View view, boolean visible) {
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Destroy created views.
+     */
+    public void clear() {
+        if (mCurrentPresenter != null) {
+            mCurrentPresenter.onUnbindViewHolder(mCurrentViewHolder);
+            mParent.removeView(mCurrentViewHolder.view);
+            mCurrentViewHolder = null;
+            mCurrentPresenter = null;
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Row.java b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
new file mode 100644
index 0000000..893d5c0
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * A row in the RowContainerFragment.  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).
+ */
+public class Row {
+
+    private static final int FLAG_ID_USE_MASK = 1;
+    private static final int FLAG_ID_USE_HEADER = 1;
+    private static final int FLAG_ID_USE_ID = 0;
+
+    private int mFlags = FLAG_ID_USE_HEADER;
+    private HeaderItem mHeaderItem;
+    private long mId = NO_ID;
+
+    public Row(long id, HeaderItem headerItem) {
+        setId(id);
+        setHeaderItem(headerItem);
+    }
+
+    public Row(HeaderItem headerItem) {
+        setHeaderItem(headerItem);
+    }
+
+    public Row() {
+    }
+
+    /**
+     * Get optional {@link HeaderItem} that represents metadata for the row.
+     */
+    public final HeaderItem getHeaderItem() {
+        return mHeaderItem;
+    }
+
+    /**
+     * Set the {@link HeaderItem} that represents metadata for the row.
+     */
+    public final void setHeaderItem(HeaderItem headerItem) {
+        mHeaderItem = headerItem;
+    }
+
+    /**
+     * Set id for this row.
+     */
+    public final void setId(long id) {
+        mId = id;
+        setFlags(FLAG_ID_USE_ID, FLAG_ID_USE_MASK);
+    }
+
+    /**
+     * Returns a unique identifier for this row.  If {@link #setId(long)}
+     * is ever called, it will return this id; else returns {@link HeaderItem#getId()}
+     * if header item is null; otherwise returns NO_ID.
+     */
+    public final long getId() {
+        if ( (mFlags & FLAG_ID_USE_MASK) == FLAG_ID_USE_HEADER) {
+            HeaderItem header = getHeaderItem();
+            if (header != null) {
+                return header.getId();
+            }
+            return NO_ID;
+        } else {
+            return mId;
+        }
+    }
+
+    final void setFlags(int flags, int mask) {
+        mFlags = (mFlags & ~mask) | (flags & mask);
+    }
+
+    final int getFlags() {
+        return mFlags;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java b/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
new file mode 100644
index 0000000..05dff40
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
@@ -0,0 +1,65 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * RowContainerView wraps header and user defined row view
+ */
+final class RowContainerView extends LinearLayout {
+
+    private ViewGroup mHeaderDock;
+
+    public RowContainerView(Context context) {
+        this(context, null, 0);
+    }
+
+    public RowContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RowContainerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setOrientation(VERTICAL);
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_row_container, this);
+
+        mHeaderDock = (ViewGroup) findViewById(R.id.lb_row_container_header_dock);
+        setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+    }
+
+    public void addHeaderView(View headerView) {
+        if (mHeaderDock.indexOfChild(headerView) < 0) {
+            mHeaderDock.addView(headerView, 0);
+        }
+    }
+
+    public void removeHeaderView(View headerView) {
+        if (mHeaderDock.indexOfChild(headerView) >= 0) {
+            mHeaderDock.removeView(headerView);
+        }
+    }
+
+    public void addRowView(View view) {
+        addView(view);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
new file mode 100644
index 0000000..b31d5f0
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.graphics.ColorOverlayDimmer;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * RowHeaderPresenter provides a default implementation for header using TextView.
+ * If subclass override and creates its own view, subclass must also override
+ * {@link #onSelectLevelChanged(ViewHolder)}.
+ */
+public class RowHeaderPresenter extends Presenter {
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        float mSelectLevel;
+        int mOriginalTextColor;
+        ColorOverlayDimmer mColorDimmer;
+        public ViewHolder(View view) {
+            super(view);
+        }
+        public final float getSelectLevel() {
+            return mSelectLevel;
+        }
+    }
+
+    @Override
+    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        RowHeaderView headerView = new RowHeaderView(parent.getContext());
+        ViewHolder viewHolder = new ViewHolder(headerView);
+        viewHolder.mOriginalTextColor = headerView.getCurrentTextColor();
+        return viewHolder;
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        setSelectLevel((ViewHolder) viewHolder, 0);
+        Row rowItem = (Row) item;
+        if (rowItem != null) {
+            HeaderItem headerItem = rowItem.getHeaderItem();
+            if (headerItem != null) {
+                String text = headerItem.getName();
+                ((RowHeaderView) viewHolder.view).setText(text);
+            }
+        }
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        ((RowHeaderView) viewHolder.view).setText(null);
+    }
+
+    public final void setSelectLevel(ViewHolder holder, float selectLevel) {
+        holder.mSelectLevel = selectLevel;
+        onSelectLevelChanged(holder);
+    }
+
+    protected void onSelectLevelChanged(ViewHolder holder) {
+        if (holder.mColorDimmer == null) {
+            holder.mColorDimmer = ColorOverlayDimmer.createDefault(holder.view.getContext());
+        }
+        holder.mColorDimmer.setActiveLevel(holder.mSelectLevel);
+        final RowHeaderView headerView = (RowHeaderView) holder.view;
+        headerView.setTextColor(holder.mColorDimmer.applyToColor(holder.mOriginalTextColor));
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
new file mode 100644
index 0000000..0a8f98e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
@@ -0,0 +1,38 @@
+/*
+ * 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.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * RowHeaderView is a header text view.
+ */
+public final class RowHeaderView extends TextView {
+
+    public RowHeaderView(Context context) {
+        this(context, null);
+    }
+
+    public RowHeaderView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.rowHeaderStyle);
+    }
+
+    public RowHeaderView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
new file mode 100644
index 0000000..787597f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -0,0 +1,386 @@
+/*
+ * 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.app.HeadersFragment;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A presenter that renders {@link Row}.
+ *
+ * <h3>Customize UI widgets</h3>
+ * When subclass of RowPresenter adds UI widgets,  it should subclass
+ * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
+ * and {@link #initializeRowViewHolder(ViewHolder)}.  Subclass must use layout id
+ * "row_content" for the widget that will be aligned to title of {@link HeadersFragment}.
+ * RowPresenter contains an optional and replaceable {@link RowHeaderPresenter} that
+ * renders header.  User can disable default rendering or replace with a new header presenter
+ * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
+ *
+ * <h3>UI events from fragments</h3>
+ * In addition to {@link Presenter} which defines how to render and bind data to row view,
+ * RowPresenter receives calls from upper level(typically a fragment) when:
+ * <ul>
+ * <li>
+ * Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
+ * is triggered immediately when there is a row selection change before the selection
+ * animation is started.
+ * Subclass of RowPresenter may override and add more works in
+ * {@link #onRowViewSelected(ViewHolder, boolean)}.
+ * </li>
+ * <li>
+ * Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}.
+ * The event is triggered immediately before the expand animation is started.
+ * Subclass of RowPresenter may override and add more works in
+ * {@link #onRowViewExpanded(ViewHolder, boolean)}.
+ * </li>
+ * </ul>
+ *
+ * <h3>User events:</h3>
+ * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}.
+ * If subclass wants to add its own {@link View.OnFocusChangeListener} or
+ * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
+ * to be properly chained by framework.  Adding view listeners after
+ * {@link #createRowViewHolder(ViewGroup)} will interfere framework's listeners.
+ *
+ * <h3>Selection animation</h3>
+ * <p>
+ * When user scrolls through rows,  fragment will initiate animation and call
+ * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value 0~1.  By default, fragment
+ * draws a dim overlay on top of row view for views not selected.  Subclass may override
+ * this default effect by having {@link #isUsingDefaultSelectEffect()} return false
+ * and override {@link #onSelectLevelChanged(ViewHolder)} to apply its own selection effect.
+ * </p>
+ * <p>
+ * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable select effect,
+ * This is not only for enable/disable default dim implementation but also subclass must
+ * respect this flag.
+ * </p>
+ */
+public abstract class RowPresenter extends Presenter {
+
+    static class ContainerViewHolder extends Presenter.ViewHolder {
+        /**
+         * wrapped row view holder
+         */
+        final ViewHolder mRowViewHolder;
+
+        public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) {
+            super(containerView);
+            containerView.addRowView(rowViewHolder.view);
+            if (rowViewHolder.mHeaderViewHolder != null) {
+                containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
+            }
+            mRowViewHolder = rowViewHolder;
+            mRowViewHolder.mContainerViewHolder = this;
+        }
+    }
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        ContainerViewHolder mContainerViewHolder;
+        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
+        Row mRow;
+        boolean mSelected;
+        boolean mExpanded;
+        boolean mInitialzed;
+        float mSelectLevel = 0f; // initially unselected
+        public ViewHolder(View view) {
+            super(view);
+        }
+        public final Row getRow() {
+            return mRow;
+        }
+        public final boolean isExpanded() {
+            return mExpanded;
+        }
+        public final boolean isSelected() {
+            return mSelected;
+        }
+        public final float getSelectLevel() {
+            return mSelectLevel;
+        }
+        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
+            return mHeaderViewHolder;
+        }
+    }
+
+    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+
+    boolean mSelectEffectEnabled = true;
+
+    @Override
+    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        ViewHolder vh = createRowViewHolder(parent);
+        vh.mInitialzed = false;
+        Presenter.ViewHolder result;
+        if (needsRowContainerView()) {
+            RowContainerView containerView = new RowContainerView(parent.getContext());
+            if (mHeaderPresenter != null) {
+                vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
+                        mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
+            }
+            result = new ContainerViewHolder(containerView, vh);
+        } else {
+            result = vh;
+        }
+        initializeRowViewHolder(vh);
+        if (!vh.mInitialzed) {
+            throw new RuntimeException("super.initializeRowViewHolder() must be called");
+        }
+        return result;
+    }
+
+    /**
+     * Called to create a ViewHolder object for row,  subclass of {@link RowPresenter}
+     * should override and return a different concrete ViewHolder object. 
+     */
+    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
+
+    /**
+     * Called after a {@link RowPresenter.ViewHolder} is created,
+     * subclass of {@link RowPresenter} may override this method and start with calling
+     * super.initializeRowViewHolder(ViewHolder).
+     */
+    protected void initializeRowViewHolder(ViewHolder vh) {
+        vh.mInitialzed = true;
+    }
+
+    /**
+     * Change the presenter used for rendering header. Can be null to disable header rendering.
+     * The method must be called before creating any row view.
+     */
+    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
+        mHeaderPresenter = headerPresenter;
+    }
+
+    /**
+     * Get optional presenter used for rendering header.  May return null.
+     */
+    public final RowHeaderPresenter getHeaderPresenter() {
+        return mHeaderPresenter;
+    }
+
+    /**
+     * Get wrapped {@link RowPresenter.ViewHolder}
+     */
+    public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
+        if (holder instanceof ContainerViewHolder) {
+            return ((ContainerViewHolder) holder).mRowViewHolder;
+        } else {
+            return (ViewHolder) holder;
+        }
+    }
+
+    /**
+     * Change expanded state of row view.
+     */
+    public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
+        ViewHolder rowViewHolder = getRowViewHolder(holder);
+        rowViewHolder.mExpanded = expanded;
+        onRowViewExpanded(rowViewHolder, expanded);
+    }
+
+    /**
+     * Change select state of row view.
+     */
+    public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
+        ViewHolder rowViewHolder = getRowViewHolder(holder);
+        rowViewHolder.mSelected = selected;
+        onRowViewSelected(rowViewHolder, selected);
+    }
+
+    /**
+     * Subclass may override and respond to expanded state change of row in fragment.
+     * Default implementation hide/show header view depending on expanded state.
+     * Subclass may make visual changes to row view but not allowed to create
+     * animation on the row view.
+     */
+    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
+        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
+            RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
+            View headerView = vh.mHeaderViewHolder.view;
+            if (expanded) {
+                containerView.addHeaderView(headerView);
+            } else {
+                containerView.removeHeaderView(headerView);
+            }
+        }
+    }
+
+    /**
+     * Subclass may override and respond to event Row is selected.
+     * Subclass may make visual changes to row view but not allowed to create
+     * animation on the row view.
+     */
+    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
+        if (selected && mOnItemSelectedListener != null) {
+            mOnItemSelectedListener.onItemSelected(null, vh.getRow());
+        }
+    }
+
+    /**
+     * Set current select level from 0(unselected) to 1(selected).
+     * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}.
+     */
+    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
+        ViewHolder rowViewHolder = getRowViewHolder(vh);
+        rowViewHolder.mSelectLevel = level;
+        onSelectLevelChanged(rowViewHolder);
+    }
+
+    /**
+     * Get current select level from 0(unselected) to 1(selected).
+     */
+    public final float getSelectLevel(Presenter.ViewHolder vh) {
+        return getRowViewHolder(vh).mSelectLevel;
+    }
+
+    /**
+     * Callback when select level is changed.  Default implementation applies select level
+     * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
+     * when {@link #getSelectEffectEnabled()} is true.
+     * Subclass may override this function and implements its own select effect.  When it
+     * overrides,  it should also override {@link #isUsingDefaultSelectEffect()} to disable
+     * the default dimming effect applied by framework.
+     */
+    protected void onSelectLevelChanged(ViewHolder vh) {
+        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
+        }
+    }
+
+    /**
+     * Enables or disables the row selection effect.
+     * This is not only for enable/disable default dim implementation but also subclass must
+     * respect this flag.
+     */
+    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
+        mSelectEffectEnabled = applyDimOnSelect;
+    }
+
+    /**
+     * Returns true if row selection effect is enabled.
+     * This is not only for enable/disable default dim implementation but also subclass must
+     * respect this flag.
+     */
+    public final boolean getSelectEffectEnabled() {
+        return mSelectEffectEnabled;
+    }
+
+    /**
+     * Return if using default dimming effect provided by framework (fragment).  Subclass
+     * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}.
+     */
+    public boolean isUsingDefaultSelectEffect() {
+        return true;
+    }
+
+    final boolean needsDefaultSelectEffect() {
+        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
+    }
+
+    final boolean needsRowContainerView() {
+        return mHeaderPresenter != null;
+    }
+
+    /**
+     * Return true if the Row view can draw outside bounds.
+     */
+    public boolean canDrawOutOfBounds() {
+        return false;
+    }
+
+    @Override
+    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
+    }
+
+    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
+        vh.mRow = (Row) item;
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
+        }
+    }
+
+    @Override
+    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
+    }
+
+    protected void onUnbindRowViewHolder(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
+        }
+        vh.mRow = null;
+    }
+
+    @Override
+    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
+        onRowViewAttachedToWindow(getRowViewHolder(holder));
+    }
+
+    protected void onRowViewAttachedToWindow(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
+        }
+    }
+
+    @Override
+    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
+        onRowViewDetachedFromWindow(getRowViewHolder(holder));
+    }
+
+    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
+        }
+    }
+
+    /**
+     * Set listener for item or row selection.  RowPresenter fires row selection
+     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
+     * fire a selection event with selected item.
+     */
+    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Get listener for item or row selection.
+     */
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Set listener for item click event.  RowPresenter does nothing but subclass of
+     * RowPresenter may fire item click event if it does have a concept of item.
+     * 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.
+     */
+    public final void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Set listener for item click event.
+     */
+    public final OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
new file mode 100644
index 0000000..029db3e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -0,0 +1,191 @@
+/*
+ * 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.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.support.v17.leanback.R;
+import android.widget.TextView;
+
+/**
+ * SearchBar is a search widget.
+ */
+public class SearchBar extends RelativeLayout {
+    private static final String TAG = SearchBar.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    /**
+     * Listener for search query changes
+     */
+    public interface SearchBarListener {
+
+        /**
+         * Method invoked when the search bar detects a change in the query.
+         *
+         * @param query The current full query.
+         */
+        public void onSearchQueryChange(String query);
+
+        /**
+         * Method invoked when the search query is submitted.
+         *
+         * @param query The query being submitted.
+         */
+        public void onSearchQuerySubmit(String query);
+
+        /**
+         * Method invoked when the IME is being dismissed.
+         *
+         * @param query The query set in the search bar at the time the IME is being dismissed.
+         */
+        public void onKeyboardDismiss(String query);
+    }
+
+    private SearchBarListener mSearchBarListener;
+    private SearchEditText mSearchTextEditor;
+    private String mSearchQuery;
+    private final Handler mHandler = new Handler();
+
+    public SearchBar(Context context) {
+        this(context, null);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mSearchQuery = "";
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mSearchTextEditor = (SearchEditText)findViewById(R.id.lb_search_text_editor);
+        mSearchTextEditor.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean hasFocus) {
+                if (DEBUG) Log.v(TAG, "onFocusChange " + hasFocus);
+                if (hasFocus) {
+                    showNativeKeyboard();
+                }
+            }
+        });
+        mSearchTextEditor.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+                setSearchQuery(charSequence.toString());
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+
+            }
+        });
+        mSearchTextEditor.setOnKeyboardDismissListener(
+                new SearchEditText.OnKeyboardDismissListener() {
+                    @Override
+                    public void onKeyboardDismiss() {
+                        if (null != mSearchBarListener) {
+                            mSearchBarListener.onKeyboardDismiss(mSearchQuery);
+                        }
+                    }
+                });
+        mSearchTextEditor.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView textView, int action, KeyEvent keyEvent) {
+                if (EditorInfo.IME_ACTION_SEARCH == action && null != mSearchBarListener) {
+                    mSearchBarListener.onSearchQuerySubmit(mSearchQuery);
+                    return true;
+                }
+                return false;
+            }
+        });
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mSearchTextEditor.requestFocus();
+                mSearchTextEditor.requestFocusFromTouch();
+            }
+        });
+    }
+
+    /**
+     * Set a listener for when the term search changes
+     * @param listener
+     */
+    public void setSearchBarListener(SearchBarListener listener) {
+        mSearchBarListener = listener;
+    }
+
+    /**
+     * Set the search query
+     * @param query the search query to use
+     */
+    public void setSearchQuery(String query) {
+        if (query.equals(mSearchQuery)) {
+            return;
+        }
+        mSearchQuery = query;
+        if (null != mSearchBarListener) {
+            mSearchBarListener.onSearchQueryChange(mSearchQuery);
+        }
+    }
+
+    /**
+     * Set the hint text shown in the search bar.
+     * @param hint The hint to use.
+     */
+    public void setHint(String hint) {
+        mSearchTextEditor.setHint(hint);
+    }
+
+    protected void showNativeKeyboard() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mSearchTextEditor.requestFocusFromTouch();
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+            }
+        });
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
new file mode 100644
index 0000000..41353c9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
@@ -0,0 +1,66 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+/**
+ * EditText widget that monitors keyboard changes.
+ */
+public class SearchEditText extends EditText {
+    private static final String TAG = SearchEditText.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    public interface OnKeyboardDismissListener {
+        public void onKeyboardDismiss();
+    }
+
+    private OnKeyboardDismissListener mKeyboardDismissListener;
+
+    public SearchEditText(Context context) {
+        this(context, null);
+    }
+
+    public SearchEditText(Context context, AttributeSet attrs) {
+        this(context, attrs, R.style.TextAppearance_Leanback_SearchTextEdit);
+    }
+
+    public SearchEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            if (DEBUG) Log.v(TAG, "Keyboard being dismissed");
+            mKeyboardDismissListener.onKeyboardDismiss();
+            return true;
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+
+    /**
+     * Set a keyboard dismissed listener.
+     *
+     * @param listener The listener.
+     */
+    public void setOnKeyboardDismissListener(OnKeyboardDismissListener listener) {
+        mKeyboardDismissListener = listener;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
new file mode 100644
index 0000000..1fb1c93
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
@@ -0,0 +1,90 @@
+/*
+ * 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.graphics.Rect;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class SearchOrbView extends LinearLayout implements View.OnClickListener {
+    private final static String TAG = SearchOrbView.class.getSimpleName();
+    private final static boolean DEBUG = false;
+
+    private OnClickListener mListener;
+    private LinearLayout mSearchOrbView;
+
+    public SearchOrbView(Context context) {
+        this(context, null);
+    }
+
+    public SearchOrbView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchOrbView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mSearchOrbView = (LinearLayout) inflater.inflate(R.layout.lb_search_orb, this, true);
+
+        // By default we are not visible
+        setVisibility(INVISIBLE);
+        setFocusable(true);
+        mSearchOrbView.setAlpha(0.5f);
+
+        setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (null != mListener) {
+            mListener.onClick(view);
+        }
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        if (DEBUG) Log.v(TAG, "onFocusChanged " + gainFocus + " " + direction);
+        if (gainFocus) {
+            mSearchOrbView.setAlpha(1.0f);
+        } else {
+            mSearchOrbView.setAlpha(0.5f);
+        }
+    }
+
+    /**
+     * Set the on click listener for the orb
+     * @param listener The listener.
+     */
+    public void setOnOrbClickedListener(OnClickListener listener) {
+        mListener = listener;
+        if (null != listener) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.INVISIBLE);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
new file mode 100644
index 0000000..00ed8d4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -0,0 +1,120 @@
+/*
+ * 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.os.Build;
+import android.view.ViewGroup;
+
+
+/**
+ * Helper for shadow.
+ */
+final class ShadowHelper {
+
+    final static ShadowHelper sInstance = new ShadowHelper();
+    boolean mSupportsShadow;
+    ShadowHelperVersionImpl mImpl;
+
+    /**
+     * Interface implemented by classes that support Shadow.
+     */
+    static interface ShadowHelperVersionImpl {
+
+        public void prepareParent(ViewGroup parent);
+
+        public Object addShadow(ViewGroup shadowContainer);
+
+        public void setShadowFocusLevel(Object impl, float level);
+
+    }
+
+    /**
+     * Interface used when we do not support Shadow animations.
+     */
+    private static final class ShadowHelperStubImpl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            // do nothing
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            // do nothing
+            return null;
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            // do nothing
+        }
+
+    }
+
+    /**
+     * Implementation used on JBMR2 (and above).
+     */
+    private static final class ShadowHelperJbmr2Impl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            ShadowHelperJbmr2.prepareParent(parent);
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            return ShadowHelperJbmr2.addShadow(shadowContainer);
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            ShadowHelperJbmr2.setShadowFocusLevel(impl, level);
+        }
+
+    }
+
+    /**
+     * Returns the ShadowHelper.
+     */
+    private ShadowHelper() {
+        if (Build.VERSION.SDK_INT >= 18) {
+            mSupportsShadow = true;
+            mImpl = new ShadowHelperJbmr2Impl();
+        } else {
+            mSupportsShadow = false;
+            mImpl = new ShadowHelperStubImpl();
+        }
+    }
+
+    public static ShadowHelper getInstance() {
+        return sInstance;
+    }
+
+    public boolean supportsShadow() {
+        return mSupportsShadow;
+    }
+
+    public void prepareParent(ViewGroup parent) {
+        mImpl.prepareParent(parent);
+    }
+
+    public Object addShadow(ViewGroup shadowContainer) {
+        return mImpl.addShadow(shadowContainer);
+    }
+
+    public void setShadowFocusLevel(Object impl, float level) {
+        mImpl.setShadowFocusLevel(impl, level);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
new file mode 100644
index 0000000..62bc191
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
@@ -0,0 +1,200 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * ShadowOverlayContainer Provides a SDK version independent wrapper container
+ * to take care of shadow and/or color overlay.
+ * <p>
+ * Shadow and color dimmer overlay are both optional.  When shadow is used,  it's
+ * user's responsibility to properly call setClipChildren(false) on parent views if
+ * the shadow can appear outside bounds of parent views.
+ * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+ * before using shadow.  Depending on sdk version, optical bounds might be applied
+ * to parent.
+ * </p>
+ * <p>
+ * {@link #initialize(boolean, boolean)} must be first called on the container to initialize
+ * shadows and/or color overlay.  Then call {@link #wrap(View)} to insert wrapped view
+ * into container.
+ * </p>
+ * <p>
+ * Call {@link #setShadowFocusLevel(float)} to control shadow alpha.
+ * </p>
+ * <p>
+ * Call {@link #setOverlayColor(int)} to control overlay color.
+ * </p>
+ */
+public class ShadowOverlayContainer extends ViewGroup {
+
+    private boolean mInitialized;
+    private View mColorDimOverlay;
+    private Object mShadowImpl;
+    private View mWrappedView;
+
+    public ShadowOverlayContainer(Context context) {
+        this(context, null, 0);
+    }
+
+    public ShadowOverlayContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Return true if the platform sdk supports shadow.
+     */
+    public static boolean supportsShadow() {
+        return ShadowHelper.getInstance().supportsShadow();
+    }
+
+    /**
+     * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+     * before using shadow.  Depending on sdk version, optical bounds might be applied
+     * to parent.
+     */
+    public static void prepareParentForShadow(ViewGroup parent) {
+        ShadowHelper.getInstance().prepareParent(parent);
+    }
+
+    /**
+     * Initialize shadows and/or color overlay.  Both are optional.
+     */
+    public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
+        if (mInitialized) {
+            throw new IllegalStateException();
+        }
+        mInitialized = true;
+        if (hasShadow) {
+            mShadowImpl = ShadowHelper.getInstance().addShadow(this);
+        }
+        if (hasColorDimOverlay) {
+            mColorDimOverlay = LayoutInflater.from(getContext())
+                    .inflate(R.layout.lb_card_color_overlay, this, false);
+            addView(mColorDimOverlay);
+        }
+    }
+
+    /**
+     * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
+     */
+    public void setShadowFocusLevel(float level) {
+        if (mShadowImpl != null) {
+            if (level < 0f) {
+                level = 0f;
+            } else if (level > 1f) {
+                level = 1f;
+            }
+            ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
+        }
+    }
+
+    /**
+     * Set color (with alpha) of the overlay.
+     */
+    public void setOverlayColor(int overlayColor) {
+        if (mColorDimOverlay != null) {
+            mColorDimOverlay.setBackgroundColor(overlayColor);
+        }
+    }
+
+    /**
+     * Inserts view into the wrapper.
+     */
+    public void wrap(View view) {
+        if (!mInitialized || mWrappedView != null) {
+            throw new IllegalStateException();
+        }
+        if (mColorDimOverlay != null) {
+            addView(view, indexOfChild(mColorDimOverlay));
+        } else {
+            addView(view);
+        }
+        mWrappedView = view;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mWrappedView == null) {
+            throw new IllegalStateException();
+        }
+        // padding and child margin are not supported.
+        // first measure the wrapped view, then measure the shadow view and/or overlay view.
+        int childWidthMeasureSpec, childHeightMeasureSpec;
+        LayoutParams lp = mWrappedView.getLayoutParams();
+        if (lp.width == LayoutParams.MATCH_PARENT) {
+            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
+                    (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
+        } else {
+            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
+        }
+        if (lp.height == LayoutParams.MATCH_PARENT) {
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
+                    (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
+        } else {
+            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
+        }
+        mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+        int measuredWidth = mWrappedView.getMeasuredWidth();
+        int measuredHeight = mWrappedView.getMeasuredHeight();
+
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child == mWrappedView) {
+                continue;
+            }
+            lp = child.getLayoutParams();
+            if (lp.width == LayoutParams.MATCH_PARENT) {
+                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
+                        (measuredWidth, MeasureSpec.EXACTLY);
+            } else {
+                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
+            }
+
+            if (lp.height == LayoutParams.MATCH_PARENT) {
+                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
+                        (measuredHeight, MeasureSpec.EXACTLY);
+            } else {
+                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
+            }
+            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        }
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final int width = child.getMeasuredWidth();
+                final int height = child.getMeasuredHeight();
+                child.layout(0, 0, width, height);
+            }
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
new file mode 100644
index 0000000..261b638
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+/**
+ * A {@link PresenterSelector} that always returns the same {@link Presenter}.
+ * Useful for rows of items of the same type that are all rendered the same way.
+ */
+public final class SinglePresenterSelector extends PresenterSelector {
+
+    private final Presenter mPresenter;
+
+    /**
+     * @param presenter The Presenter to return for every item.
+     */
+    public SinglePresenterSelector(Presenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        return mPresenter;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
new file mode 100644
index 0000000..b4de4a6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -0,0 +1,309 @@
+/*
+ * 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.v4.util.CircularArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A dynamic data structure that maintains staggered grid position information
+ * for each individual child. The algorithm ensures that each row will be kept
+ * as balanced as possible when prepending and appending a child.
+ *
+ * <p>
+ * You may keep view {@link StaggeredGrid.Location} inside StaggeredGrid as much
+ * as possible since prepending and appending views is not symmetric: layout
+ * going from 0 to N will likely produce a different result than layout going
+ * from N to 0 for the staggered cases. If a user scrolls from 0 to N then
+ * scrolls back to 0 and we don't keep history location information, edges of
+ * the very beginning of rows will not be aligned. It is recommended to keep a
+ * list of tens of thousands of {@link StaggeredGrid.Location}s which will be
+ * big enough to remember a typical user's scroll history. There are situations
+ * where StaggeredGrid falls back to the simple case where we do not need save a
+ * huge list of locations inside StaggeredGrid:
+ * <ul>
+ *   <li>Only one row (e.g., a single row listview)</li>
+ *   <li> Each item has the same length (not staggered at all)</li>
+ * </ul>
+ *
+ * <p>
+ * This class is abstract and can be replaced with different implementations.
+ */
+abstract class StaggeredGrid {
+
+    /**
+     * TODO: document this
+     */
+    public static interface Provider {
+        /**
+         * Return how many items are in the adapter.
+         */
+        public abstract int getCount();
+
+        /**
+         * Create the object at a given row.
+         */
+        public abstract void createItem(int index, int row, boolean append);
+    }
+
+    /**
+     * Location of an item in the grid. For now it only saves row index but
+     * more information may be added in the future.
+     */
+    public final static class Location {
+        /**
+         * The index of the row for this Location.
+         */
+        public final int row;
+
+        /**
+         * Create a Location with the given row index.
+         */
+        public Location(int row) {
+            this.row = row;
+        }
+    }
+
+    /**
+     * TODO: document this
+     */
+    public final static class Row {
+        /**
+         * first view start location
+         */
+        public int low;
+        /**
+         * last view end location
+         */
+        public int high;
+    }
+
+    protected Provider mProvider;
+    protected int mNumRows = 1; // mRows.length
+    protected Row[] mRows;
+    protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
+    private ArrayList<Integer>[] mTmpItemPositionsInRows;
+
+    /**
+     * A constant representing a default starting index, indicating that the
+     * developer did not provide a start index.
+     */
+    public static final int START_DEFAULT = -1;
+
+    // the first index that grid will layout
+    protected int mStartIndex = START_DEFAULT;
+    // the row to layout the first index
+    protected int mStartRow = START_DEFAULT;
+
+    protected int mFirstIndex = -1;
+
+    /**
+     * Sets the {@link Provider} for this staggered grid.
+     *
+     * @param provider The provider for this staggered grid.
+     */
+    public void setProvider(Provider provider) {
+        mProvider = provider;
+    }
+
+    /**
+     * Sets the array of {@link Row}s to fill into. For views that represent a
+     * horizontal list, this will be the rows of the view. For views that
+     * represent a vertical list, this will be the columns.
+     *
+     * @param row The array of {@link Row}s to be filled.
+     */
+    public final void setRows(Row[] row) {
+        if (row == null || row.length == 0) {
+            throw new IllegalArgumentException();
+        }
+        mNumRows = row.length;
+        mRows = row;
+        mTmpItemPositionsInRows = new ArrayList[mNumRows];
+        for (int i = 0; i < mNumRows; i++) {
+            mTmpItemPositionsInRows[i] = new ArrayList(32);
+        }
+    }
+
+    /**
+     * Returns the number of rows in the staggered grid.
+     */
+    public final int getNumRows() {
+        return mNumRows;
+    }
+
+    /**
+     * Set the first item index and the row index to load when there are no
+     * items.
+     *
+     * @param startIndex the index of the first item
+     * @param startRow the index of the row
+     */
+    public final void setStart(int startIndex, int startRow) {
+        mStartIndex = startIndex;
+        mStartRow = startRow;
+    }
+
+    /**
+     * Returns the first index in the staggered grid.
+     */
+    public final int getFirstIndex() {
+        return mFirstIndex;
+    }
+
+    /**
+     * Returns the last index in the staggered grid.
+     */
+    public final int getLastIndex() {
+        return mFirstIndex + mLocations.size() - 1;
+    }
+
+    /**
+     * Returns the size of the saved {@link Location}s.
+     */
+    public final int getSize() {
+        return mLocations.size();
+    }
+
+    /**
+     * Returns the {@link Location} at the given index.
+     */
+    public final Location getLocation(int index) {
+        if (mLocations.size() == 0) {
+            return null;
+        }
+        return mLocations.get(index - mFirstIndex);
+    }
+
+    /**
+     * Removes the first element.
+     */
+    public final void removeFirst() {
+        mFirstIndex++;
+        mLocations.popFirst();
+    }
+
+    /**
+     * Removes the last element.
+     */
+    public final void removeLast() {
+        mLocations.popLast();
+    }
+
+    public final void debugPrint(PrintWriter pw) {
+        for (int i = 0, size = mLocations.size(); i < size; i++) {
+            Location loc = mLocations.get(i);
+            pw.print("<" + (mFirstIndex + i) + "," + loc.row + ">");
+            pw.print(" ");
+            pw.println();
+        }
+    }
+
+    protected final int getMaxHighRowIndex() {
+        int maxHighRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].high > mRows[maxHighRowIndex].high) {
+                maxHighRowIndex = i;
+            }
+        }
+        return maxHighRowIndex;
+    }
+
+    protected final int getMinHighRowIndex() {
+        int minHighRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].high < mRows[minHighRowIndex].high) {
+                minHighRowIndex = i;
+            }
+        }
+        return minHighRowIndex;
+    }
+
+    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);
+        return loc;
+    }
+
+    /**
+     * Append items until the high edge reaches upTo.
+     */
+    public abstract void appendItems(int upTo);
+
+    protected final int getMaxLowRowIndex() {
+        int maxLowRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].low > mRows[maxLowRowIndex].low) {
+                maxLowRowIndex = i;
+            }
+        }
+        return maxLowRowIndex;
+    }
+
+    protected final int getMinLowRowIndex() {
+        int minLowRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].low < mRows[minLowRowIndex].low) {
+                minLowRowIndex = i;
+            }
+        }
+        return minLowRowIndex;
+    }
+
+    protected final Location prependItemToRow(int itemIndex, int rowIndex) {
+        mProvider.createItem(itemIndex, rowIndex, false);
+        Location loc = new Location(rowIndex);
+        mFirstIndex = itemIndex;
+        mLocations.addFirst(loc);
+        return loc;
+    }
+
+    /**
+     * Return array of Lists for all rows, each List contains item positions
+     * on that row between startPos(included) and endPositions(included).
+     * Returned value is read only, do not change it.
+     */
+    public final List<Integer>[] getItemPositionsInRows(int startPos, int endPos) {
+        for (int i = 0; i < mNumRows; i++) {
+            mTmpItemPositionsInRows[i].clear();
+        }
+        if (startPos >= 0) {
+            for (int i = startPos; i <= endPos; i++) {
+                mTmpItemPositionsInRows[getLocation(i).row].add(i);
+            }
+        }
+        return mTmpItemPositionsInRows;
+    }
+
+    /**
+     * Prepend items until the low edge reaches downTo.
+     */
+    public abstract void prependItems(int downTo);
+
+    /**
+     * Strip items, keep a contiguous subset of items; the subset should include
+     * at least one item on every row that currently has at least one item.
+     *
+     * <p>
+     * TODO: document this better
+     */
+    public abstract void stripDownTo(int itemIndex);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
new file mode 100644
index 0000000..9f2a06c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+/**
+ * A default implementation of {@link StaggeredGrid}.
+ *
+ * This implementation tries to fill items in consecutive row order. The next
+ * item is always in same row or in the next row.
+ */
+final class StaggeredGridDefault extends StaggeredGrid {
+
+    @Override
+    public void appendItems(int upTo) {
+        int count = mProvider.getCount();
+        int itemIndex;
+        int rowIndex;
+        if (mLocations.size() > 0) {
+            itemIndex = getLastIndex() + 1;
+            rowIndex = (mLocations.getLast().row + 1) % mNumRows;
+        } else {
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
+        }
+
+    top_loop:
+        while (true) {
+            // find highest row (.high is biggest)
+            int maxHighRowIndex = mLocations.size() > 0 ? getMaxHighRowIndex() : -1;
+            int maxHigh = maxHighRowIndex != -1 ? mRows[maxHighRowIndex].high : Integer.MIN_VALUE;
+            // fill from current row till last row so that each row will grow longer than
+            // the previous highest row.
+            for (; rowIndex < mNumRows; rowIndex++) {
+                // fill one item to a row
+                if (itemIndex == count) {
+                    break top_loop;
+                }
+                appendItemToRow(itemIndex++, rowIndex);
+                // fill more item to the row to make sure this row is longer than
+                // the previous highest row.
+                if (maxHighRowIndex == -1) {
+                    maxHighRowIndex = getMaxHighRowIndex();
+                    maxHigh = mRows[maxHighRowIndex].high;
+                } else  if (rowIndex != maxHighRowIndex) {
+                    while (mRows[rowIndex].high < maxHigh) {
+                        if (itemIndex == count) {
+                            break top_loop;
+                        }
+                        appendItemToRow(itemIndex++, rowIndex);
+                    }
+                }
+            }
+            if (mRows[getMinHighRowIndex()].high >= upTo) {
+                break;
+            }
+            // start fill from row 0 again
+            rowIndex = 0;
+        }
+    }
+
+    @Override
+    public void prependItems(int downTo) {
+        if (mProvider.getCount() <= 0) return;
+        int itemIndex;
+        int rowIndex;
+        if (mLocations.size() > 0) {
+            itemIndex = getFirstIndex() - 1;
+            rowIndex = mLocations.getFirst().row;
+            if (rowIndex == 0) {
+                rowIndex = mNumRows - 1;
+            } else {
+                rowIndex--;
+            }
+        } else {
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
+        }
+
+    top_loop:
+        while (true) {
+            int minLowRowIndex = mLocations.size() > 0 ? getMinLowRowIndex() : -1;
+            int minLow = minLowRowIndex != -1 ? mRows[minLowRowIndex].low : Integer.MAX_VALUE;
+            for (; rowIndex >=0 ; rowIndex--) {
+                if (itemIndex < 0) {
+                    break top_loop;
+                }
+                prependItemToRow(itemIndex--, rowIndex);
+                if (minLowRowIndex == -1) {
+                    minLowRowIndex = getMinLowRowIndex();
+                    minLow = mRows[minLowRowIndex].low;
+                } else if (rowIndex != minLowRowIndex) {
+                    while (mRows[rowIndex].low > minLow) {
+                        if (itemIndex < 0) {
+                            break top_loop;
+                        }
+                        prependItemToRow(itemIndex--, rowIndex);
+                    }
+                }
+            }
+            if (mRows[getMaxLowRowIndex()].low <= downTo) {
+                break;
+            }
+            rowIndex = mNumRows - 1;
+        }
+    }
+
+    @Override
+    public final void stripDownTo(int itemIndex) {
+        // because we layout the items in the order that next item is either same row
+        // or next row,  so we can easily find the row range by searching items forward and
+        // backward until we see the row is 0 or mNumRow - 1
+        Location loc = getLocation(itemIndex);
+        if (loc == null) {
+            return;
+        }
+        int firstIndex = getFirstIndex();
+        int lastIndex = getLastIndex();
+        int row = loc.row;
+
+        int endIndex = itemIndex;
+        int endRow = row;
+        while (endRow < mNumRows - 1 && endIndex < lastIndex) {
+            endIndex++;
+            endRow = getLocation(endIndex).row;
+        }
+
+        int startIndex = itemIndex;
+        int startRow = row;
+        while (startRow > 0 && startIndex > firstIndex) {
+            startIndex--;
+            startRow = getLocation(startIndex).row;
+        }
+        // trim information
+        for (int i = firstIndex; i < startIndex; i++) {
+            removeFirst();
+        }
+        for (int i = endIndex; i < lastIndex; i++) {
+            removeLast();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
new file mode 100644
index 0000000..586ebf9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
@@ -0,0 +1,244 @@
+/*
+ * 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.util.Log;
+
+/**
+ * A presenter that renders objects in a vertical grid.
+ *
+ */
+public class VerticalGridPresenter extends Presenter {
+    private static final String TAG = "GridPresenter";
+    private static final boolean DEBUG = false;
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        final VerticalGridView mGridView;
+        boolean mInitialized;
+
+        public ViewHolder(VerticalGridView view) {
+            super(view);
+            mGridView = view;
+        }
+
+        public VerticalGridView getGridView() {
+            return mGridView;
+        }
+    }
+
+    private int mNumColumns = -1;
+    private int mZoomFactor;
+    private boolean mShadowEnabled = true;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+
+    public VerticalGridPresenter() {
+        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
+    }
+
+    public VerticalGridPresenter(int zoomFactor) {
+        mZoomFactor = zoomFactor;
+    }
+
+    /**
+     * Sets the number of columns in the vertical grid.
+     */
+    public void setNumberOfColumns(int numColumns) {
+        if (numColumns < 0) {
+            throw new IllegalArgumentException("Invalid number of columns");
+        }
+        if (mNumColumns != numColumns) {
+            mNumColumns = numColumns;
+        }
+    }
+
+    /**
+     * Returns the number of columns in the vertical grid.
+     */
+    public int getNumberOfColumns() {
+        return mNumColumns;
+    }
+
+    /**
+     * Enable or disable child shadow.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final void setShadowEnabled(boolean enabled) {
+        mShadowEnabled = enabled;
+    }
+
+    /**
+     * Returns true if child shadow is enabled.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final boolean getShadowEnabled() {
+        return mShadowEnabled;
+    }
+
+    /**
+     * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow
+     * is applied to each individual child of {@link VerticalGridView}.
+     * Subclass may return false to disable.
+     */
+    public boolean isUsingDefaultShadow() {
+        return ShadowOverlayContainer.supportsShadow();
+    }
+
+    final boolean needsDefaultShadow() {
+        return isUsingDefaultShadow() && getShadowEnabled();
+    }
+
+    @Override
+    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
+        ViewHolder vh = createGridViewHolder(parent);
+        vh.mInitialized = false;
+        initializeGridViewHolder(vh);
+        if (!vh.mInitialized) {
+            throw new RuntimeException("super.initializeGridViewHolder() must be called");
+        }
+        return vh;
+    }
+
+    /**
+     * Subclass may override this to inflate a different layout.
+     */
+    protected ViewHolder createGridViewHolder(ViewGroup parent) {
+        View root = LayoutInflater.from(parent.getContext()).inflate(
+                R.layout.lb_vertical_grid, parent, false);
+        return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid));
+    }
+
+    private ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public View createWrapper(View root) {
+            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
+            wrapper.setLayoutParams(
+                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            wrapper.initialize(needsDefaultShadow(), false);
+            return wrapper;
+        }
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+        }
+    };
+
+    protected void initializeGridViewHolder(ViewHolder vh) {
+        if (mNumColumns == -1) {
+            throw new IllegalStateException("Number of columns must be set");
+        }
+        if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns);
+        vh.getGridView().setNumColumns(mNumColumns);
+        vh.mInitialized = true;
+
+        if (needsDefaultShadow()) {
+            vh.mItemBridgeAdapter.setWrapper(mWrapper);
+            ShadowOverlayContainer.prepareParentForShadow(vh.getGridView());
+            ((ViewGroup) vh.view).setClipChildren(false);
+        }
+        FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter, mZoomFactor);
+
+        final ViewHolder gridViewHolder = vh;
+        vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectChildView(gridViewHolder, view);
+            }
+        });
+
+        vh.mItemBridgeAdapter.setAdapterListener(new ItemBridgeAdapter.AdapterListener() {
+            @Override
+            public void onCreate(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
+                // Only when having an OnItemClickListner, we attach the OnClickListener.
+                if (getOnItemClickedListener() != null) {
+                    final View itemView = itemViewHolder.getViewHolder().view;
+                    itemView.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            if (getOnItemClickedListener() != null) {
+                                // Row is always null
+                                getOnItemClickedListener().onItemClicked(itemViewHolder.mItem, null);
+                            }
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        if (DEBUG) Log.v(TAG, "onBindViewHolder " + item);
+        ViewHolder vh = (ViewHolder) viewHolder;
+        vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item);
+        vh.getGridView().setAdapter(vh.mItemBridgeAdapter);
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        if (DEBUG) Log.v(TAG, "onUnbindViewHolder");
+        ViewHolder vh = (ViewHolder) viewHolder;
+        vh.mItemBridgeAdapter.setAdapter(null);
+        vh.getGridView().setAdapter(null);
+    }
+
+    /**
+     * Sets the item selected listener.
+     * Since this is a grid the row parameter is always null.
+     */
+    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Returns the item selected listener.
+     */
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Sets the item clicked listener.
+     * 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.
+     */
+    public final void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public final OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    private void selectChildView(ViewHolder vh, View view) {
+        if (getOnItemSelectedListener() != null) {
+            ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null :
+                (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view);
+
+            getOnItemSelectedListener().onItemSelected(ibh == null ? null : ibh.mItem, null);
+        }
+    };
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
new file mode 100644
index 0000000..0b3f453
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
@@ -0,0 +1,65 @@
+/*
+ * 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.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+
+/**
+ * A view that shows items in a vertically scrolling list. The items come from
+ * the {@link RecyclerView.Adapter} associated with this view.
+ */
+public class VerticalGridView extends BaseGridView {
+
+    public VerticalGridView(Context context) {
+        this(context, null);
+    }
+
+    public VerticalGridView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VerticalGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager.setOrientation(RecyclerView.VERTICAL);
+        initAttributes(context, attrs);
+    }
+
+    protected void initAttributes(Context context, AttributeSet attrs) {
+        initBaseGridViewAttributes(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbVerticalGridView);
+        setColumnWidth(a.getDimensionPixelSize(R.styleable.lbVerticalGridView_columnWidth, 0));
+        setNumColumns(a.getInt(R.styleable.lbVerticalGridView_numberOfColumns, 1));
+        a.recycle();
+    }
+
+    /**
+     * Set the number of columns.
+     */
+    public void setNumColumns(int numColumns) {
+        mLayoutManager.setNumRows(numColumns);
+        requestLayout();
+    }
+
+    /**
+     * Set the column width.
+     */
+    public void setColumnWidth(int width) {
+        mLayoutManager.setRowHeight(width);
+        requestLayout();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
new file mode 100644
index 0000000..7d79fc5
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+
+/**
+ * Maintains Window Alignment information of two axis.
+ */
+class WindowAlignment {
+
+    /**
+     * Maintains alignment information in one direction.
+     */
+    public static class Axis {
+        /**
+         * mScrollCenter is used to calculate dynamic transformation based on how far a view
+         * is from the mScrollCenter. For example, the views with center close to mScrollCenter
+         * will be scaled up.
+         */
+        private float mScrollCenter;
+        /** 
+         * Right or bottom edge of last child. 
+         */
+        private int mMaxEdge;
+        /** 
+         * Left or top edge of first child, typically should be zero.
+         */
+        private int mMinEdge;
+
+        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
+
+        private int mWindowAlignmentOffset = 0;
+
+        private float mWindowAlignmentOffsetPercent = 50f;
+
+        private int mSize;
+
+        private int mPaddingLow;
+
+        private int mPaddingHigh;
+
+        private String mName; // for debugging
+
+        public Axis(String name) {
+            reset();
+            mName = name;
+        }
+
+        final public int getWindowAlignment() {
+            return mWindowAlignment;
+        }
+
+        final public void setWindowAlignment(int windowAlignment) {
+            mWindowAlignment = windowAlignment;
+        }
+
+        final public int getWindowAlignmentOffset() {
+            return mWindowAlignmentOffset;
+        }
+
+        final public void setWindowAlignmentOffset(int offset) {
+            mWindowAlignmentOffset = offset;
+        }
+
+        final public void setWindowAlignmentOffsetPercent(float percent) {
+            if ((percent < 0 || percent > 100)
+                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                throw new IllegalArgumentException();
+            }
+            mWindowAlignmentOffsetPercent = percent;
+        }
+
+        final public float getWindowAlignmentOffsetPercent() {
+            return mWindowAlignmentOffsetPercent;
+        }
+
+        final public int getScrollCenter() {
+            return (int) mScrollCenter;
+        }
+
+        /** set minEdge,  Integer.MIN_VALUE means unknown*/
+        final public void setMinEdge(int minEdge) {
+            mMinEdge = minEdge;
+        }
+
+        public void invalidateScrollMin() {
+            mMinEdge = Integer.MIN_VALUE;
+        }
+
+        /** update max edge,  Integer.MAX_VALUE means unknown*/
+        final public void setMaxEdge(int maxEdge) {
+            mMaxEdge = maxEdge;
+        }
+
+        public void invalidateScrollMax() {
+            mMaxEdge = Integer.MAX_VALUE;
+        }
+
+        final public float updateScrollCenter(float scrollTarget) {
+            mScrollCenter = scrollTarget;
+            return scrollTarget;
+        }
+
+        private void reset() {
+            mScrollCenter = Integer.MIN_VALUE;
+            mMinEdge = Integer.MIN_VALUE;
+            mMaxEdge = Integer.MAX_VALUE;
+        }
+
+        final public boolean isMinUnknown() {
+            return mMinEdge == Integer.MIN_VALUE;
+        }
+
+        final public boolean isMaxUnknown() {
+            return mMaxEdge == Integer.MAX_VALUE;
+        }
+
+        final public void setSize(int size) {
+            mSize = size;
+        }
+
+        final public int getSize() {
+            return mSize;
+        }
+
+        final public void setPadding(int paddingLow, int paddingHigh) {
+            mPaddingLow = paddingLow;
+            mPaddingHigh = paddingHigh;
+        }
+
+        final public int getPaddingLow() {
+            return mPaddingLow;
+        }
+
+        final public int getPaddingHigh() {
+            return mPaddingHigh;
+        }
+
+        final public int getClientSize() {
+            return mSize - mPaddingLow - mPaddingHigh;
+        }
+
+        final public int getSystemScrollPos() {
+            return getSystemScrollPos((int) mScrollCenter);
+        }
+
+        final public int getSystemScrollPos(int scrollCenter) {
+            int middlePosition;
+            if (mWindowAlignmentOffset >= 0) {
+                middlePosition = mWindowAlignmentOffset - mPaddingLow;
+            } else {
+                middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
+            }
+            if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+            }
+            int clientSize = getClientSize();
+            int afterMiddlePosition = clientSize - middlePosition;
+            boolean isMinUnknown = isMinUnknown();
+            boolean isMaxUnknown = isMaxUnknown();
+            if (!isMinUnknown && !isMaxUnknown &&
+                    (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
+                if (mMaxEdge - mMinEdge <= clientSize) {
+                    // total children size is less than view port and we want to align
+                    // both edge:  align first child to left edge of view port
+                    return mMinEdge - mPaddingLow;
+                }
+            }
+            if (!isMinUnknown) {
+                if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 &&
+                        scrollCenter - mMinEdge <= middlePosition) {
+                    // scroll center is within half of view port size: align the left edge
+                    // of first child to the left edge of view port
+                    return mMinEdge - mPaddingLow;
+                }
+            }
+            if (!isMaxUnknown) {
+                if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 &&
+                        mMaxEdge - scrollCenter <= afterMiddlePosition) {
+                    // scroll center is very close to the right edge of view port : align the
+                    // right edge of last children (plus expanded size) to view port's right
+                    return mMaxEdge -mPaddingLow - (clientSize);
+                }
+            }
+            // else put scroll center in middle of view port
+            return scrollCenter - middlePosition - mPaddingLow;
+        }
+
+        @Override
+        public String toString() {
+            return "center: " + mScrollCenter + " min:" + mMinEdge +
+                    " max:" + mMaxEdge;
+        }
+
+    }
+
+    private int mOrientation = HORIZONTAL;
+
+    final public Axis vertical = new Axis("vertical");
+
+    final public Axis horizontal = new Axis("horizontal");
+
+    private Axis mMainAxis = horizontal;
+
+    private Axis mSecondAxis = vertical;
+
+    final public Axis mainAxis() {
+        return mMainAxis;
+    }
+
+    final public Axis secondAxis() {
+        return mSecondAxis;
+    }
+
+    final public void setOrientation(int orientation) {
+        mOrientation = orientation;
+        if (mOrientation == HORIZONTAL) {
+            mMainAxis = horizontal;
+            mSecondAxis = vertical;
+        } else {
+            mMainAxis = vertical;
+            mSecondAxis = horizontal;
+        }
+    }
+
+    final public int getOrientation() {
+        return mOrientation;
+    }
+
+    final public void reset() {
+        mainAxis().reset();
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuffer().append("horizontal=")
+                .append(horizontal.toString())
+                .append("vertical=")
+                .append(vertical.toString())
+                .toString();
+    }
+
+}
diff --git a/v4/Android.mk b/v4/Android.mk
index 0cdb05d..526a1c6 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -133,10 +133,22 @@
 
 # -----------------------------------------------------------------------
 
+# A helper sub-library that makes direct use of the upcoming API
+# TODO: Apply a real name and SDK version when available
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api20
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api20)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-kitkat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # Here is the final static library that apps can link against.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4
 LOCAL_SDK_VERSION := 4
 LOCAL_SRC_FILES := $(call all-java-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-kitkat
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api20
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v4/api20/android/support/v4/app/NotificationCompatApi20.java b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
new file mode 100644
index 0000000..e737695
--- /dev/null
+++ b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
@@ -0,0 +1,85 @@
+/*
+ * 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.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+class NotificationCompatApi20 {
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int mProgressMax, int mProgress, boolean mProgressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                Bundle extras) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(mProgressMax, mProgress, mProgressIndeterminate)
+                .setLocalOnly(localOnly)
+                .setExtras(extras);
+        }
+
+        @Override
+        public void addAction(int icon, CharSequence title, PendingIntent intent) {
+            b.addAction(icon, title, intent);
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            return b.build();
+        }
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return (notif.flags & Notification.FLAG_LOCAL_ONLY) != 0;
+    }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index e432d56..d39c5db 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -18,9 +18,10 @@
 def jbMr1SS        = createApiSourceset('jellybeanmr1', 'jellybean-mr1', '17',      jbSS)
 def jbMr2SS        = createApiSourceset('jellybeanmr2', 'jellybean-mr2', '18',      jbMr1SS)
 def kitkatSS       = createApiSourceset('kitkat',       'kitkat',        '19',      jbMr2SS)
+def api20SS        = createApiSourceset('api20',        'api20',         'current', kitkatSS)
 
 // setup the main code to depend on the above through the highest platform-specific sourceset.
-setupDependencies('compile', kitkatSS)
+setupDependencies('compile', api20SS)
 // --------------------------
 
 def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
@@ -52,10 +53,10 @@
 }
 
 dependencies {
+    compile project(':support-annotations')
     compile getAndroidPrebuilt('4')
 }
 
-
 uploadArchives {
     repositories {
         mavenDeployer {
diff --git a/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java b/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java
new file mode 100644
index 0000000..4de2e21
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java
@@ -0,0 +1,28 @@
+/*
+ * 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.app;
+
+import android.app.Notification;
+
+/**
+ * Interface implemented by notification compat builders that support
+ * an accessor for {@link Notification.Builder}. {@link Notification.Builder}
+ * was introduced in HoneyComb.
+ */
+interface NotificationBuilderWithBuilderAccessor {
+    public Notification.Builder getBuilder();
+}
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index a237831..0a1bb57 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -52,4 +52,12 @@
     public static int getMeasuredState(View view) {
         return view.getMeasuredState();
     }
+
+    public static float getTranslationX(View view) {
+        return view.getTranslationX();
+    }
+
+    public static float getTranslationY(View view) {
+        return view.getTranslationY();
+    }
 }
diff --git a/v4/java/android/support/v4/app/ActionBarDrawerToggle.java b/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
index 5c7e733..7589fa9 100644
--- a/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
+++ b/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
@@ -24,6 +24,9 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.DrawerLayout;
@@ -64,6 +67,7 @@
          * @return Delegate to use for ActionBarDrawableToggles, or null if the Activity
          *         does not wish to override the default behavior.
          */
+        @Nullable
         Delegate getDrawerToggleDelegate();
     }
 
@@ -72,6 +76,7 @@
          * @return Up indicator drawable as defined in the Activity's theme, or null if one is not
          *         defined.
          */
+        @Nullable
         Drawable getThemeUpIndicator();
 
         /**
@@ -80,14 +85,14 @@
          * @param upDrawable     - Drawable to set as up indicator
          * @param contentDescRes - Content description to set
          */
-        void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes);
+        void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes);
 
         /**
          * Set the Action Bar's up indicator content description.
          *
          * @param contentDescRes - Content description to set
          */
-        void setActionBarDescription(int contentDescRes);
+        void setActionBarDescription(@StringRes int contentDescRes);
     }
 
     private interface ActionBarDrawerToggleImpl {
@@ -211,7 +216,8 @@
      *                                  for accessibility
      */
     public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
-            int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
+            @DrawableRes int drawerImageRes, @StringRes int openDrawerContentDescRes,
+            @StringRes int closeDrawerContentDescRes) {
         mActivity = activity;
 
         // Allow the Activity to provide an impl
diff --git a/v4/java/android/support/v4/app/ActivityCompat.java b/v4/java/android/support/v4/app/ActivityCompat.java
index a30eff2..f456a1b 100644
--- a/v4/java/android/support/v4/app/ActivityCompat.java
+++ b/v4/java/android/support/v4/app/ActivityCompat.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
 
 /**
@@ -84,7 +85,7 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivity(Activity activity, Intent intent, Bundle options) {
+    public static void startActivity(Activity activity, Intent intent, @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             ActivityCompatJB.startActivity(activity, intent, options);
         } else {
@@ -112,7 +113,8 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
+    public static void startActivityForResult(Activity activity, Intent intent, int requestCode,
+            @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             ActivityCompatJB.startActivityForResult(activity, intent, requestCode, options);
         } else {
diff --git a/v4/java/android/support/v4/app/DialogFragment.java b/v4/java/android/support/v4/app/DialogFragment.java
index 8c67bf5..3c7773a 100644
--- a/v4/java/android/support/v4/app/DialogFragment.java
+++ b/v4/java/android/support/v4/app/DialogFragment.java
@@ -21,12 +21,18 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Static library support version of the framework's {@link android.app.DialogFragment}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
@@ -37,6 +43,11 @@
 public class DialogFragment extends Fragment
         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
 
+    /** @hide */
+    @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface DialogStyle {}
+
     /**
      * Style for {@link #setStyle(int, int)}: a basic,
      * normal dialog.
@@ -98,7 +109,7 @@
      * @param theme Optional custom theme.  If 0, an appropriate theme (based
      * on the style) will be selected for you.
      */
-    public void setStyle(int style, int theme) {
+    public void setStyle(@DialogStyle int style, @StyleRes int theme) {
         mStyle = style;
         if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
             mTheme = android.R.style.Theme_Panel;
@@ -195,6 +206,7 @@
         return mDialog;
     }
 
+    @StyleRes
     public int getTheme() {
         return mTheme;
     }
@@ -333,6 +345,7 @@
      * 
      * @return Return a new Dialog instance to be displayed by the Fragment.
      */
+    @NonNull
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         return new Dialog(getActivity(), getTheme());
     }
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 621bbbb..ab23595 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.util.SimpleArrayMap;
 import android.support.v4.util.DebugUtils;
 import android.util.AttributeSet;
@@ -609,7 +611,7 @@
      *
      * @param resId Resource id for the CharSequence text
      */
-    public final CharSequence getText(int resId) {
+    public final CharSequence getText(@StringRes int resId) {
         return getResources().getText(resId);
     }
 
@@ -619,7 +621,7 @@
      *
      * @param resId Resource id for the string
      */
-    public final String getString(int resId) {
+    public final String getString(@StringRes int resId) {
         return getResources().getString(resId);
     }
 
@@ -632,7 +634,7 @@
      * @param formatArgs The format arguments that will be used for substitution.
      */
 
-    public final String getString(int resId, Object... formatArgs) {
+    public final String getString(@StringRes int resId, Object... formatArgs) {
         return getResources().getString(resId, formatArgs);
     }
 
@@ -1013,8 +1015,8 @@
      * 
      * @return Return the View for the fragment's UI, or null.
      */
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
         return null;
     }
 
@@ -1028,7 +1030,7 @@
      * @param savedInstanceState If non-null, this fragment is being re-constructed
      * from a previous saved state as given here.
      */
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     }
 
     /**
@@ -1037,6 +1039,7 @@
      * 
      * @return The fragment's root view, or null if it has no layout.
      */
+    @Nullable
     public View getView() {
         return mView;
     }
@@ -1054,7 +1057,7 @@
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
-    public void onActivityCreated(Bundle savedInstanceState) {
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         mCalled = true;
     }
 
@@ -1069,7 +1072,7 @@
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
-    public void onViewStateRestored(Bundle savedInstanceState) {
+    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
         mCalled = true;
     }
 
@@ -1198,6 +1201,7 @@
         mRestored = false;
         mBackStackNesting = 0;
         mFragmentManager = null;
+        mChildFragmentManager = null;
         mActivity = null;
         mFragmentId = 0;
         mContainerId = 0;
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 596653a..ad57bb8 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
 import android.support.v4.util.SimpleArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -238,7 +239,7 @@
      * Add support for inflating the &lt;fragment> tag.
      */
     @Override
-    public View onCreateView(String name, Context context, AttributeSet attrs) {
+    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
         if (!"fragment".equals(name)) {
             return super.onCreateView(name, context, attrs);
         }
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index f07d3dd..2def8ed 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -23,6 +23,8 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.IdRes;
+import android.support.annotation.StringRes;
 import android.support.v4.util.DebugUtils;
 import android.support.v4.util.LogWriter;
 import android.util.Log;
@@ -90,12 +92,14 @@
          * Return the full bread crumb title resource identifier for the entry,
          * or 0 if it does not have one.
          */
+        @StringRes
         public int getBreadCrumbTitleRes();
 
         /**
          * Return the short bread crumb title resource identifier for the entry,
          * or 0 if it does not have one.
          */
+        @StringRes
         public int getBreadCrumbShortTitleRes();
 
         /**
@@ -164,7 +168,7 @@
      * on the back stack associated with this ID are searched.
      * @return The fragment if found or null otherwise.
      */
-    public abstract Fragment findFragmentById(int id);
+    public abstract Fragment findFragmentById(@IdRes int id);
 
     /**
      * Finds a fragment that was identified by the given tag either when inflated
@@ -392,7 +396,7 @@
  * Callbacks from FragmentManagerImpl to its container.
  */
 interface FragmentContainer {
-    public View findViewById(int id);
+    public View findViewById(@IdRes int id);
 }
 
 /**
@@ -1079,7 +1083,9 @@
                                     makeInactive(f);
                                 } else {
                                     f.mActivity = null;
+                                    f.mParentFragment = null;
                                     f.mFragmentManager = null;
+                                    f.mChildFragmentManager = null;
                                 }
                             }
                         }
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 23fedf9..d984d36 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -16,6 +16,16 @@
 
 package android.support.v4.app;
 
+import android.support.annotation.AnimRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Static library support version of the framework's {@link android.app.FragmentTransaction}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
@@ -32,7 +42,7 @@
     /**
      * Calls {@link #add(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Add a fragment to the activity state.  This fragment may optionally
@@ -49,12 +59,13 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
+            @Nullable String tag);
     
     /**
      * Calls {@link #replace(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Replace an existing fragment that was added to a container.  This is
@@ -72,7 +83,8 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment,
+            @Nullable String tag);
     
     /**
      * Remove an existing fragment.  If it was added to a container, its view
@@ -146,7 +158,12 @@
      * Bit mask that is set for all exit transitions.
      */
     public static final int TRANSIT_EXIT_MASK = 0x2000;
-    
+
+    /** @hide */
+    @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Transit {}
+
     /** Not set up for a transition. */
     public static final int TRANSIT_UNSET = -1;
     /** No animation for transition. */
@@ -164,7 +181,8 @@
      * entering and exiting in this transaction. These animations will not be
      * played when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
+    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
+            @AnimRes int exit);
 
     /**
      * Set specific animation resources to run for the fragments that are
@@ -172,21 +190,21 @@
      * and <code>popExit</code> animations will be played for enter/exit
      * operations specifically when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit,
-            int popEnter, int popExit);
+    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
+            @AnimRes int exit, @AnimRes int popEnter, @AnimRes int popExit);
     
     /**
      * Select a standard transition animation for this transaction.  May be
      * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
      * or {@link #TRANSIT_FRAGMENT_CLOSE}
      */
-    public abstract FragmentTransaction setTransition(int transit);
+    public abstract FragmentTransaction setTransition(@Transit int transit);
 
     /**
      * Set a custom style resource that will be used for resolving transit
      * animations.
      */
-    public abstract FragmentTransaction setTransitionStyle(int styleRes);
+    public abstract FragmentTransaction setTransitionStyle(@StyleRes int styleRes);
     
     /**
      * Add this transaction to the back stack.  This means that the transaction
@@ -195,7 +213,7 @@
      *
      * @param name An optional name for this back stack state, or null.
      */
-    public abstract FragmentTransaction addToBackStack(String name);
+    public abstract FragmentTransaction addToBackStack(@Nullable String name);
 
     /**
      * Returns true if this FragmentTransaction is allowed to be added to the back
@@ -219,7 +237,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
@@ -234,7 +252,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbShortTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
diff --git a/v4/java/android/support/v4/app/NavUtils.java b/v4/java/android/support/v4/app/NavUtils.java
index ea034be..841bc56 100644
--- a/v4/java/android/support/v4/app/NavUtils.java
+++ b/v4/java/android/support/v4/app/NavUtils.java
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.support.annotation.Nullable;
 import android.support.v4.content.IntentCompat;
 import android.util.Log;
 
@@ -274,6 +275,7 @@
      * @return The fully qualified class name of sourceActivity's parent activity or null if
      *         it was not specified
      */
+    @Nullable
     public static String getParentActivityName(Activity sourceActivity) {
         try {
             return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
@@ -292,6 +294,7 @@
      * @return The fully qualified class name of sourceActivity's parent activity or null if
      *         it was not specified
      */
+    @Nullable
     public static String getParentActivityName(Context context, ComponentName componentName)
             throws NameNotFoundException {
         PackageManager pm = context.getPackageManager();
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index f2bc034..5ea5db6 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -24,6 +24,7 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.widget.RemoteViews;
 import java.util.ArrayList;
 
@@ -32,6 +33,90 @@
  * introduced after API level 4 in a backwards compatible fashion.
  */
 public class NotificationCompat {
+
+    /**
+     * Use all default values (where applicable).
+     */
+    public static final int DEFAULT_ALL = ~0;
+
+    /**
+     * Use the default notification sound. This will ignore any sound set using
+     * {@link Builder#setSound}
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_SOUND = 1;
+
+    /**
+     * Use the default notification vibrate. This will ignore any vibrate set using
+     * {@link Builder#setVibrate}. Using phone vibration requires the
+     * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_VIBRATE = 2;
+
+    /**
+     * Use the default notification lights. This will ignore the
+     * {@link #FLAG_SHOW_LIGHTS} bit, and values set with {@link Builder#setLights}.
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_LIGHTS = 4;
+
+    /**
+     * Use this constant as the value for audioStreamType to request that
+     * the default stream type for notifications be used.  Currently the
+     * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
+     */
+    public static final int STREAM_DEFAULT = -1;
+
+    /**
+     * Bit set in the Notification flags field when LEDs should be turned on
+     * for this notification.
+     */
+    public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
+
+    /**
+     * Bit set in the Notification flags field if this notification is in
+     * reference to something that is ongoing, like a phone call.  It should
+     * not be set if this notification is in reference to something that
+     * happened at a particular point in time, like a missed phone call.
+     */
+    public static final int FLAG_ONGOING_EVENT      = 0x00000002;
+
+    /**
+     * Bit set in the Notification flags field if
+     * the audio will be repeated until the notification is
+     * cancelled or the notification window is opened.
+     */
+    public static final int FLAG_INSISTENT          = 0x00000004;
+
+    /**
+     * Bit set in the Notification flags field if the notification's sound,
+     * vibrate and ticker should only be played if the notification is not already showing.
+     */
+    public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
+
+    /**
+     * Bit set in the Notification flags field if the notification should be canceled when
+     * it is clicked by the user.
+     */
+    public static final int FLAG_AUTO_CANCEL        = 0x00000010;
+
+    /**
+     * Bit set in the Notification flags field if the notification should not be canceled
+     * when the user clicks the Clear all button.
+     */
+    public static final int FLAG_NO_CLEAR           = 0x00000020;
+
+    /**
+     * Bit set in the Notification flags field if this notification represents a currently
+     * running service.  This will normally be set for you by
+     * {@link android.app.Service#startForeground}.
+     */
+    public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
+
     /**
      * Obsolete flag indicating high-priority notifications; use the priority field instead.
      *
@@ -76,15 +161,119 @@
      */
     public static final int PRIORITY_MAX = 2;
 
+    /**
+     * Notification extras key: this is the title of the notification,
+     * as supplied to {@link Builder#setContentTitle(CharSequence)}.
+     */
+    public static final String EXTRA_TITLE = "android.title";
+
+    /**
+     * Notification extras key: this is the title of the notification when shown in expanded form,
+     * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
+     */
+    public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
+
+    /**
+     * Notification extras key: this is the main text payload, as supplied to
+     * {@link Builder#setContentText(CharSequence)}.
+     */
+    public static final String EXTRA_TEXT = "android.text";
+
+    /**
+     * Notification extras key: this is a third line of text, as supplied to
+     * {@link Builder#setSubText(CharSequence)}.
+     */
+    public static final String EXTRA_SUB_TEXT = "android.subText";
+
+    /**
+     * Notification extras key: this is a small piece of additional text as supplied to
+     * {@link Builder#setContentInfo(CharSequence)}.
+     */
+    public static final String EXTRA_INFO_TEXT = "android.infoText";
+
+    /**
+     * Notification extras key: this is a line of summary information intended to be shown
+     * alongside expanded notifications, as supplied to (e.g.)
+     * {@link BigTextStyle#setSummaryText(CharSequence)}.
+     */
+    public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+
+    /**
+     * Notification extras key: this is the resource ID of the notification's main small icon, as
+     * supplied to {@link Builder#setSmallIcon(int)}.
+     */
+    public static final String EXTRA_SMALL_ICON = "android.icon";
+
+    /**
+     * Notification extras key: this is a bitmap to be used instead of the small icon when showing the
+     * notification payload, as
+     * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+
+    /**
+     * Notification extras key: this is a bitmap to be used instead of the one from
+     * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
+     * shown in its expanded form, as supplied to
+     * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
+
+    /**
+     * Notification extras key: this is the progress value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS = "android.progress";
+
+    /**
+     * Notification extras key: this is the maximum value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+
+    /**
+     * Notification extras key: whether the progress bar is indeterminate, supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+
+    /**
+     * Notification extras key: whether the when field set using {@link Builder#setWhen} should
+     * be shown as a count-up timer (specifically a {@link android.widget.Chronometer}) instead
+     * of a timestamp, as supplied to {@link Builder#setUsesChronometer(boolean)}.
+     */
+    public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+
+    /**
+     * Notification extras key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
+     * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_PICTURE = "android.picture";
+
+    /**
+     * Notification extras key: An array of CharSequences to show in {@link InboxStyle} expanded
+     * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
+     */
+    public static final String EXTRA_TEXT_LINES = "android.textLines";
+
+    /**
+     * Notification extras key: An array of people that this notification relates to, specified
+     * by contacts provider contact URI.
+     */
+    public static final String EXTRA_PEOPLE = "android.people";
+
     private static final NotificationCompatImpl IMPL;
 
     interface NotificationCompatImpl {
         public Notification build(Builder b);
+        public Bundle getExtras(Notification n);
+        public boolean getLocalOnly(Notification n);
     }
 
     static class NotificationCompatImplBase implements NotificationCompatImpl {
+        @Override
         public Notification build(Builder b) {
-            Notification result = (Notification) b.mNotification;
+            Notification result = b.mNotification;
             result.setLatestEventInfo(b.mContext, b.mContentTitle,
                     b.mContentText, b.mContentIntent);
             // translate high priority requests into legacy flag
@@ -93,11 +282,22 @@
             }
             return result;
         }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return null;
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return false;
+        }
     }
 
     static class NotificationCompatImplGingerbread extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
-            Notification result = (Notification) b.mNotification;
+            Notification result = b.mNotification;
             result.setLatestEventInfo(b.mContext, b.mContentTitle,
                     b.mContentText, b.mContentIntent);
             result = NotificationCompatGingerbread.add(result, b.mContext,
@@ -110,7 +310,8 @@
         }
     }
 
-    static class NotificationCompatImplHoneycomb implements NotificationCompatImpl {
+    static class NotificationCompatImplHoneycomb extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
             return NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
                     b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
@@ -118,7 +319,8 @@
         }
     }
 
-    static class NotificationCompatImplIceCreamSandwich implements NotificationCompatImpl {
+    static class NotificationCompatImplIceCreamSandwich extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
             return NotificationCompatIceCreamSandwich.add(b.mContext, b.mNotification,
                     b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
@@ -127,45 +329,120 @@
         }
     }
 
-    static class NotificationCompatImplJellybean implements NotificationCompatImpl {
+    static class NotificationCompatImplJellybean extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
-            NotificationCompatJellybean jbBuilder = new NotificationCompatJellybean(
+            NotificationCompatJellybean.Builder builder = new NotificationCompatJellybean.Builder(
                     b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
                     b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                     b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
-                    b.mUseChronometer, b.mPriority, b.mSubText);
-            for (Action action: b.mActions) {
-                jbBuilder.addAction(action.icon, action.title, action.actionIntent);
+                    b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return NotificationCompatJellybean.getExtras(n);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatJellybean.getLocalOnly(n);
+        }
+    }
+
+    static class NotificationCompatImplKitKat extends NotificationCompatImplBase {
+        @Override
+        public Notification build(Builder b) {
+            NotificationCompatKitKat.Builder builder = new NotificationCompatKitKat.Builder(
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+                    b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+                    b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
+                    b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return NotificationCompatKitKat.getExtras(n);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatKitKat.getLocalOnly(n);
+        }
+    }
+
+    static class NotificationCompatImplApi20 extends NotificationCompatImplBase {
+        @Override
+        public Notification build(Builder b) {
+            NotificationCompatApi20.Builder builder = new NotificationCompatApi20.Builder(
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+                    b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+                    b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
+                    b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return NotificationCompatKitKat.getExtras(n);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatApi20.getLocalOnly(n);
+        }
+    }
+
+    private static void addActionsToBuilder(NotificationBuilderWithActions builder,
+            ArrayList<Action> actions) {
+        for (Action action : actions) {
+            builder.addAction(action.icon, action.title, action.actionIntent);
+        }
+    }
+
+    private static void addStyleToBuilderJellybean(NotificationBuilderWithBuilderAccessor builder,
+            Style style) {
+        if (style != null) {
+            if (style instanceof BigTextStyle) {
+                BigTextStyle bigTextStyle = (BigTextStyle) style;
+                NotificationCompatJellybean.addBigTextStyle(builder,
+                        bigTextStyle.mBigContentTitle,
+                        bigTextStyle.mSummaryTextSet,
+                        bigTextStyle.mSummaryText,
+                        bigTextStyle.mBigText);
+            } else if (style instanceof InboxStyle) {
+                InboxStyle inboxStyle = (InboxStyle) style;
+                NotificationCompatJellybean.addInboxStyle(builder,
+                        inboxStyle.mBigContentTitle,
+                        inboxStyle.mSummaryTextSet,
+                        inboxStyle.mSummaryText,
+                        inboxStyle.mTexts);
+            } else if (style instanceof BigPictureStyle) {
+                BigPictureStyle bigPictureStyle = (BigPictureStyle) style;
+                NotificationCompatJellybean.addBigPictureStyle(builder,
+                        bigPictureStyle.mBigContentTitle,
+                        bigPictureStyle.mSummaryTextSet,
+                        bigPictureStyle.mSummaryText,
+                        bigPictureStyle.mPicture,
+                        bigPictureStyle.mBigLargeIcon,
+                        bigPictureStyle.mBigLargeIconSet);
             }
-            if (b.mStyle != null) {
-                if (b.mStyle instanceof BigTextStyle) {
-                    BigTextStyle style = (BigTextStyle) b.mStyle;
-                    jbBuilder.addBigTextStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mBigText);
-                } else if (b.mStyle instanceof InboxStyle) {
-                    InboxStyle style = (InboxStyle) b.mStyle;
-                    jbBuilder.addInboxStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mTexts);
-                } else if (b.mStyle instanceof BigPictureStyle) {
-                    BigPictureStyle style = (BigPictureStyle) b.mStyle;
-                    jbBuilder.addBigPictureStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mPicture,
-                            style.mBigLargeIcon,
-                            style.mBigLargeIconSet);
-                }
-            }
-            return(jbBuilder.build());
         }
     }
 
     static {
-        if (Build.VERSION.SDK_INT >= 16) {
+        // TODO: Add NotificationCompatApi20 when SDK_INT is incremented.
+        if (Build.VERSION.SDK_INT >= 19) {
+            IMPL = new NotificationCompatImplKitKat();
+        } else if (Build.VERSION.SDK_INT >= 16) {
             IMPL = new NotificationCompatImplJellybean();
         } else if (Build.VERSION.SDK_INT >= 14) {
             IMPL = new NotificationCompatImplIceCreamSandwich();
@@ -217,6 +494,8 @@
         int mProgress;
         boolean mProgressIndeterminate;
         ArrayList<Action> mActions = new ArrayList<Action>();
+        boolean mLocalOnly = false;
+        Bundle mExtras;
 
         Notification mNotification = new Notification();
 
@@ -444,7 +723,7 @@
         /**
          * Set the sound to play.  It will play on the stream you supply.
          *
-         * @see #STREAM_DEFAULT
+         * @see Notification#STREAM_DEFAULT
          * @see AudioManager for the <code>STREAM_</code> constants.
          */
         public Builder setSound(Uri sound, int streamType) {
@@ -516,6 +795,17 @@
         }
 
         /**
+         * Set whether or not this notification is only relevant to the current device.
+         *
+         * <p>Some notifications can be bridged to other devices for remote display.
+         * This hint can be set to recommend this notification not be bridged.
+         */
+        public Builder setLocalOnly(boolean b) {
+            mLocalOnly = b;
+            return this;
+        }
+
+        /**
          * Set the default notification options that will be used.
          * <p>
          * The value should be one or more of the following fields combined with
@@ -551,6 +841,12 @@
          * interrupted for a higher-priority notification.
          * The system sets a notification's priority based on various factors including the
          * setPriority value. The effect may differ slightly on different platforms.
+         *
+         * @param pri Relative priority for this notification. Must be one of
+         *     the priority constants defined by {@link NotificationCompat}.
+         *     Acceptable values range from {@link
+         *     NotificationCompat#PRIORITY_MIN} (-2) to {@link
+         *     NotificationCompat#PRIORITY_MAX} (2).
          */
         public Builder setPriority(int pri) {
             mPriority = pri;
@@ -558,6 +854,56 @@
         }
 
         /**
+         * Merge additional metadata into this notification.
+         *
+         * <p>Values within the Bundle will replace existing extras values in this Builder.
+         *
+         * @see Notification#extras
+         */
+        public Builder addExtras(Bundle bag) {
+            if (mExtras == null) {
+                mExtras = new Bundle(bag);
+            } else {
+                mExtras.putAll(bag);
+            }
+            return this;
+        }
+
+        /**
+         * Set metadata for this notification.
+         *
+         * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
+         * current contents are copied into the Notification each time {@link #build()} is
+         * called.
+         *
+         * <p>Replaces any existing extras values with those from the provided Bundle.
+         * Use {@link #addExtras} to merge in metadata instead.
+         *
+         * @see Notification#extras
+         */
+        public Builder setExtras(Bundle bag) {
+            mExtras = bag;
+            return this;
+        }
+
+        /**
+         * Get the current metadata Bundle used by this notification Builder.
+         *
+         * <p>The returned Bundle is shared with this Builder.
+         *
+         * <p>The current contents of this Bundle are copied into the Notification each time
+         * {@link #build()} is called.
+         *
+         * @see Notification#extras
+         */
+        public Bundle getExtras() {
+            if (mExtras == null) {
+                mExtras = new Bundle();
+            }
+            return mExtras;
+        }
+
+        /**
          * Add an action to this notification. Actions are typically displayed by
          * the system as a button adjacent to the notification content.
          * <br>
@@ -601,7 +947,7 @@
          */
         @Deprecated
         public Notification getNotification() {
-            return (Notification) IMPL.build(this);
+            return IMPL.build(this);
         }
 
         /**
@@ -609,7 +955,7 @@
          * object.
          */
         public Notification build() {
-            return (Notification) IMPL.build(this);
+            return IMPL.build(this);
         }
     }
 
@@ -620,8 +966,7 @@
      * If the platform does not provide rich notification styles, methods in this class have no
      * effect.
      */
-    public static abstract class Style
-    {
+    public static abstract class Style {
         Builder mBuilder;
         CharSequence mBigContentTitle;
         CharSequence mSummaryText;
@@ -844,4 +1189,23 @@
             this.actionIntent = intent_;
         }
     }
+
+    /**
+     * Gets the {@link Notification#extras} field from a notification in a backwards
+     * compatible manner. Extras field was supported from JellyBean (Api level 16)
+     * forwards. This function will return null on older api levels.
+     */
+    public static Bundle getExtras(Notification notif) {
+        return IMPL.getExtras(notif);
+    }
+
+    /**
+     * Get whether or not this notification is only relevant to the current device.
+     *
+     * <p>Some notifications can be bridged to other devices for remote display.
+     * If this hint is set, it is recommend that this notification not be bridged.
+     */
+    public static boolean getLocalOnly(Notification notif) {
+        return IMPL.getLocalOnly(notif);
+    }
 }
diff --git a/v4/java/android/support/v4/app/NotificationCompatExtras.java b/v4/java/android/support/v4/app/NotificationCompatExtras.java
new file mode 100644
index 0000000..de6e8cd
--- /dev/null
+++ b/v4/java/android/support/v4/app/NotificationCompatExtras.java
@@ -0,0 +1,31 @@
+/*
+ * 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.app;
+
+/**
+ * Well-known extras used by {@link NotificationCompat} for backwards compatibility.
+ */
+public final class NotificationCompatExtras {
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value of
+     * the {@code Notification.FLAG_LOCAL_ONLY} field before it was available.
+     * If possible, use {@link NotificationCompat#getLocalOnly} instead.
+     */
+    public static final String EXTRA_LOCAL_ONLY = NotificationCompatJellybean.EXTRA_LOCAL_ONLY;
+
+    private NotificationCompatExtras() {}
+}
diff --git a/v4/java/android/support/v4/app/ShareCompat.java b/v4/java/android/support/v4/app/ShareCompat.java
index 52c4b12..87ebc49 100644
--- a/v4/java/android/support/v4/app/ShareCompat.java
+++ b/v4/java/android/support/v4/app/ShareCompat.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.StringRes;
 import android.support.v4.content.IntentCompat;
 import android.support.v4.view.MenuItemCompat;
 import android.text.Html;
@@ -403,7 +404,7 @@
          * @param resId Resource ID of the title string to use
          * @return This IntentBuilder for method chaining
          */
-        public IntentBuilder setChooserTitle(int resId) {
+        public IntentBuilder setChooserTitle(@StringRes int resId) {
             return setChooserTitle(mActivity.getText(resId));
         }
 
diff --git a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
index 2170ab6..14f150a 100644
--- a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
+++ b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
@@ -108,10 +108,14 @@
      * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcast. This obtains
      * the current state from {@link ConnectivityManager} instead of using the
      * potentially-stale value from
-     * {@link ConnectivityManager#EXTRA_NETWORK_INFO}.
+     * {@link ConnectivityManager#EXTRA_NETWORK_INFO}. May be {@code null}.
      */
     public static NetworkInfo getNetworkInfoFromBroadcast(ConnectivityManager cm, Intent intent) {
         final NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
-        return cm.getNetworkInfo(info.getType());
+        if (info != null) {
+            return cm.getNetworkInfo(info.getType());
+        } else {
+            return null;
+        }
     }
 }
diff --git a/v4/java/android/support/v4/text/TextUtilsCompat.java b/v4/java/android/support/v4/text/TextUtilsCompat.java
index 3400866..436d72f 100644
--- a/v4/java/android/support/v4/text/TextUtilsCompat.java
+++ b/v4/java/android/support/v4/text/TextUtilsCompat.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.text;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
 
 import java.util.Locale;
@@ -27,7 +29,8 @@
      * @param s the string to be encoded
      * @return the encoded string
      */
-    public static String htmlEncode(String s) {
+    @NonNull
+    public static String htmlEncode(@NonNull String s) {
         StringBuilder sb = new StringBuilder();
         char c;
         for (int i = 0; i < s.length(); i++) {
@@ -69,7 +72,7 @@
      *
      * Be careful: this code will need to be updated when vertical scripts will be supported
      */
-    public static int getLayoutDirectionFromLocale(Locale locale) {
+    public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
         if (locale != null && !locale.equals(ROOT)) {
             final String scriptSubtag = ICUCompat.getScript(
                     ICUCompat.addLikelySubtags(locale.toString()));
diff --git a/v4/java/android/support/v4/util/CircularArray.java b/v4/java/android/support/v4/util/CircularArray.java
new file mode 100644
index 0000000..91a27da
--- /dev/null
+++ b/v4/java/android/support/v4/util/CircularArray.java
@@ -0,0 +1,126 @@
+/*
+ * 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.util;
+
+/**
+ * A circular array implementation that provides O(1) random read and O(1)
+ * prepend and O(1) append.
+ */
+public class CircularArray<E>
+{
+    private E[] mElements;
+    private int mHead;
+    private int mTail;
+    private int mCapacityBitmask;
+
+    private void doubleCapacity() {
+        int n = mElements.length;
+        int r = n - mHead;
+        int newCapacity = n << 1;
+        if (newCapacity < 0) {
+            throw new RuntimeException("Too big");
+        }
+        Object[] a = new Object[newCapacity];
+        System.arraycopy(mElements, mHead, a, 0, r);
+        System.arraycopy(mElements, 0, a, r, mHead);
+        mElements = (E[])a;
+        mHead = 0;
+        mTail = n;
+        mCapacityBitmask = newCapacity - 1;
+    }
+
+    /**
+     * Create a CircularArray with default capacity.
+     */
+    public CircularArray() {
+        this(8);
+    }
+
+    /**
+     * Create a CircularArray with capacity for at least minCapacity elements.
+     *
+     * @param minCapacity The minimum capacity required for the circular array.
+     */
+    public CircularArray(int minCapacity) {
+        if (minCapacity <= 0) {
+            throw new IllegalArgumentException("capacity must be positive");
+        }
+        int arrayCapacity = minCapacity;
+        // If minCapacity isn't a power of 2, round up to the next highest power
+        // of 2.
+        if (Integer.bitCount(minCapacity) != 1) {
+            arrayCapacity = 1 << (Integer.highestOneBit(minCapacity) + 1);
+        }
+        mCapacityBitmask = arrayCapacity - 1;
+        mElements = (E[]) new Object[arrayCapacity];
+    }
+
+    public final void addFirst(E e) {
+        mHead = (mHead - 1) & mCapacityBitmask;
+        mElements[mHead] = e;
+        if (mHead == mTail) {
+            doubleCapacity();
+        }
+    }
+
+    public final void addLast(E e) {
+        mElements[mTail] = e;
+        mTail = (mTail + 1) & mCapacityBitmask;
+        if (mTail == mHead) {
+            doubleCapacity();
+        }
+    }
+
+    public final E popFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        E result = mElements[mHead];
+        mElements[mHead] = null;
+        mHead = (mHead + 1) & mCapacityBitmask;
+        return result;
+    }
+
+    public final E popLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        int t = (mTail - 1) & mCapacityBitmask;
+        E result = mElements[t];
+        mElements[t] = null;
+        mTail = t;
+        return result;
+    }
+
+    public final E getFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[mHead];
+    }
+
+    public final E getLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[(mTail - 1) & mCapacityBitmask];
+    }
+
+    public final E get(int i) {
+        if (i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException();
+        int p = (mHead + i) & mCapacityBitmask;
+        return mElements[p];
+    }
+
+    public final int size() {
+        return (mTail - mHead) & mCapacityBitmask;
+    }
+
+    public final boolean isEmpty() {
+        return mHead == mTail;
+    }
+
+}
diff --git a/v4/java/android/support/v4/util/Pools.java b/v4/java/android/support/v4/util/Pools.java
new file mode 100644
index 0000000..e3907a1
--- /dev/null
+++ b/v4/java/android/support/v4/util/Pools.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/view/PagerTabStrip.java b/v4/java/android/support/v4/view/PagerTabStrip.java
index 21488b8..834035c 100644
--- a/v4/java/android/support/v4/view/PagerTabStrip.java
+++ b/v4/java/android/support/v4/view/PagerTabStrip.java
@@ -21,6 +21,8 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -136,7 +138,7 @@
      *
      * @param resId Resource ID of a color resource to load
      */
-    public void setTabIndicatorColorResource(int resId) {
+    public void setTabIndicatorColorResource(@ColorRes int resId) {
         setTabIndicatorColor(getContext().getResources().getColor(resId));
     }
 
@@ -180,7 +182,7 @@
     }
 
     @Override
-    public void setBackgroundResource(int resId) {
+    public void setBackgroundResource(@DrawableRes int resId) {
         super.setBackgroundResource(resId);
         if (!mDrawFullUnderlineSet) {
             mDrawFullUnderline = resId == 0;
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index bb64afc..abfee23 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -21,17 +21,32 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 /**
  * Helper for accessing features in {@link View} introduced after API
  * level 4 in a backwards compatible fashion.
  */
 public class ViewCompat {
+    private static final String TAG = "ViewCompat";
+
+    /** @hide */
+    @IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_IF_CONTENT_SCROLLS})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface OverScroll {}
+
     /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
@@ -51,6 +66,16 @@
 
     private static final long FAKE_FRAME_TIME = 10;
 
+    /** @hide */
+    @IntDef({
+            IMPORTANT_FOR_ACCESSIBILITY_AUTO,
+            IMPORTANT_FOR_ACCESSIBILITY_YES,
+            IMPORTANT_FOR_ACCESSIBILITY_NO,
+            IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ImportantForAccessibility {}
+
     /**
      * Automatically determine whether a view is important for accessibility.
      */
@@ -72,6 +97,15 @@
      */
     public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004;
 
+    /** @hide */
+    @IntDef({
+            ACCESSIBILITY_LIVE_REGION_NONE,
+            ACCESSIBILITY_LIVE_REGION_POLITE,
+            ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface AccessibilityLiveRegion {}
+
     /**
      * Live region mode specifying that accessibility services should not
      * automatically announce changes to this view. This is the default live
@@ -97,6 +131,11 @@
      */
     public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002;
 
+    /** @hide */
+    @IntDef({LAYER_TYPE_NONE, LAYER_TYPE_SOFTWARE, LAYER_TYPE_HARDWARE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LayerType {}
+
     /**
      * Indicates that the view does not have a layer.
      */
@@ -145,6 +184,23 @@
      */
     public static final int LAYER_TYPE_HARDWARE = 2;
 
+    /** @hide */
+    @IntDef({
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL,
+            LAYOUT_DIRECTION_INHERIT,
+            LAYOUT_DIRECTION_LOCALE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LayoutDirectionMode {}
+
+    /** @hide */
+    @IntDef({
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ResolvedLayoutDirectionMode {}
+
     /**
      * Horizontal layout direction of this view is from Left to Right.
      */
@@ -229,9 +285,22 @@
         public int getMeasuredState(View view);
         public int getAccessibilityLiveRegion(View view);
         public void setAccessibilityLiveRegion(View view, int mode);
+        public int getPaddingStart(View view);
+        public int getPaddingEnd(View view);
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom);
+        public void dispatchStartTemporaryDetach(View view);
+        public void dispatchFinishTemporaryDetach(View view);
+        public float getTranslationX(View view);
+        public float getTranslationY(View view);
+        public int getMinimumWidth(View view);
+        public int getMinimumHeight(View view);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
+        private Method mDispatchStartTemporaryDetach;
+        private Method mDispatchFinishTemporaryDetach;
+        private boolean mTempDetachBound;
+
         public boolean canScrollHorizontally(View v, int direction) {
             return false;
         }
@@ -264,10 +333,10 @@
             // Do nothing; API doesn't exist
         }
         public void postInvalidateOnAnimation(View view) {
-            view.postInvalidateDelayed(getFrameTime());
+            view.invalidate();
         }
         public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) {
-            view.postInvalidateDelayed(getFrameTime(), left, top, right, bottom);
+            view.invalidate(left, top, right, bottom);
         }
         public void postOnAnimation(View view, Runnable action) {
             view.postDelayed(action, getFrameTime());
@@ -361,6 +430,87 @@
         public void setAccessibilityLiveRegion(View view, int mode) {
             // No-op
         }
+
+        @Override
+        public int getPaddingStart(View view) {
+            return view.getPaddingLeft();
+        }
+
+        @Override
+        public int getPaddingEnd(View view) {
+            return view.getPaddingRight();
+        }
+
+        @Override
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+            view.setPadding(start, top, end, bottom);
+        }
+
+        @Override
+        public void dispatchStartTemporaryDetach(View view) {
+            if (!mTempDetachBound) {
+                bindTempDetach();
+            }
+            if (mDispatchStartTemporaryDetach != null) {
+                try {
+                    mDispatchStartTemporaryDetach.invoke(view);
+                } catch (Exception e) {
+                    Log.d(TAG, "Error calling dispatchStartTemporaryDetach", e);
+                }
+            } else {
+                // Try this instead
+                view.onStartTemporaryDetach();
+            }
+        }
+
+        @Override
+        public void dispatchFinishTemporaryDetach(View view) {
+            if (!mTempDetachBound) {
+                bindTempDetach();
+            }
+            if (mDispatchFinishTemporaryDetach != null) {
+                try {
+                    mDispatchFinishTemporaryDetach.invoke(view);
+                } catch (Exception e) {
+                    Log.d(TAG, "Error calling dispatchFinishTemporaryDetach", e);
+                }
+            } else {
+                // Try this instead
+                view.onFinishTemporaryDetach();
+            }
+        }
+
+        private void bindTempDetach() {
+            try {
+                mDispatchStartTemporaryDetach = View.class.getDeclaredMethod(
+                        "dispatchStartTemporaryDetach");
+                mDispatchFinishTemporaryDetach = View.class.getDeclaredMethod(
+                        "dispatchFinishTemporaryDetach");
+            } catch (NoSuchMethodException e) {
+                Log.e(TAG, "Couldn't find method", e);
+            }
+            mTempDetachBound = true;
+        }
+
+        @Override
+        public float getTranslationX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getTranslationY(View view) {
+            return 0;
+        }
+
+        @Override
+        public int getMinimumWidth(View view) {
+            return 0;
+        }
+
+        @Override
+        public int getMinimumHeight(View view) {
+            return 0;
+        }
     }
 
     static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -422,6 +572,14 @@
         public int getMeasuredState(View view) {
             return ViewCompatHC.getMeasuredState(view);
         }
+        @Override
+        public float getTranslationX(View view) {
+            return ViewCompatHC.getTranslationX(view);
+        }
+        @Override
+        public float getTranslationY(View view) {
+            return ViewCompatHC.getTranslationY(view);
+        }
     }
 
     static class ICSViewCompatImpl extends HCViewCompatImpl {
@@ -482,6 +640,12 @@
         }
         @Override
         public void setImportantForAccessibility(View view, int mode) {
+            // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS is not available
+            // on this platform so replace with IMPORTANT_FOR_ACCESSIBILITY_NO
+            // which is closer semantically.
+            if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+                mode = IMPORTANT_FOR_ACCESSIBILITY_NO;
+            }
             ViewCompatJB.setImportantForAccessibility(view, mode);
         }
         @Override
@@ -501,6 +665,16 @@
         public ViewParent getParentForAccessibility(View view) {
             return ViewCompatJB.getParentForAccessibility(view);
         }
+
+        @Override
+        public int getMinimumWidth(View view) {
+            return ViewCompatJB.getMinimumWidth(view);
+        }
+
+        @Override
+        public int getMinimumHeight(View view) {
+            return ViewCompatJB.getMinimumHeight(view);
+        }
     }
 
     static class JbMr1ViewCompatImpl extends JBViewCompatImpl {
@@ -529,6 +703,21 @@
         public void setLayoutDirection(View view, int layoutDirection) {
             ViewCompatJellybeanMr1.setLayoutDirection(view, layoutDirection);
         }
+
+        @Override
+        public int getPaddingStart(View view) {
+            return ViewCompatJellybeanMr1.getPaddingStart(view);
+        }
+
+        @Override
+        public int getPaddingEnd(View view) {
+            return ViewCompatJellybeanMr1.getPaddingEnd(view);
+        }
+
+        @Override
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+            ViewCompatJellybeanMr1.setPaddingRelative(view, start, top, end, bottom);
+        }
     }
 
     static class KitKatViewCompatImpl extends JbMr1ViewCompatImpl {
@@ -541,6 +730,11 @@
         public void setAccessibilityLiveRegion(View view, int mode) {
             ViewCompatKitKat.setAccessibilityLiveRegion(view, mode);
         }
+
+        @Override
+        public void setImportantForAccessibility(View view, int mode) {
+            ViewCompatJB.setImportantForAccessibility(view, mode);
+        }
     }
 
     static final ViewCompatImpl IMPL;
@@ -594,6 +788,7 @@
      * @param v The View against which to invoke the method.
      * @return This view's over-scroll mode.
      */
+    @OverScroll
     public static int getOverScrollMode(View v) {
         return IMPL.getOverScrollMode(v);
     }
@@ -610,7 +805,7 @@
      * @param v The View against which to invoke the method.
      * @param overScrollMode The new over-scroll mode for this view.
      */
-    public static void setOverScrollMode(View v, int overScrollMode) {
+    public static void setOverScrollMode(View v, @OverScroll int overScrollMode) {
         IMPL.setOverScrollMode(v, overScrollMode);
     }
 
@@ -833,6 +1028,7 @@
      * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
      */
+    @ImportantForAccessibility
     public static int getImportantForAccessibility(View view) {
         return IMPL.getImportantForAccessibility(view);
     }
@@ -841,6 +1037,12 @@
      * Sets how to determine whether this view is important for accessibility
      * which is if it fires accessibility events and if it is reported to
      * accessibility services that query the screen.
+     * <p>
+     * <em>Note:</em> If the current paltform version does not support the
+     *  {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} mode, then
+     *  {@link #IMPORTANT_FOR_ACCESSIBILITY_NO} will be used as it is the
+     *  closest terms of semantics.
+     * </p>
      *
      * @param view The view whose property to set.
      * @param mode How to determine whether this view is important for accessibility.
@@ -850,7 +1052,8 @@
      * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
      */
-    public static void setImportantForAccessibility(View view, int mode) {
+    public static void setImportantForAccessibility(View view,
+            @ImportantForAccessibility int mode) {
         IMPL.setImportantForAccessibility(view, mode);
     }
 
@@ -949,7 +1152,7 @@
      *        and can be null. It is ignored when the layer type is
      *        {@link #LAYER_TYPE_NONE}
      */
-    public static void setLayerType(View view, int layerType, Paint paint) {
+    public static void setLayerType(View view, @LayerType int layerType, Paint paint) {
         IMPL.setLayerType(view, layerType, paint);
     }
 
@@ -969,6 +1172,7 @@
      * @see #LAYER_TYPE_SOFTWARE
      * @see #LAYER_TYPE_HARDWARE
      */
+    @LayerType
     public static int getLayerType(View view) {
         return IMPL.getLayerType(view);
     }
@@ -991,7 +1195,7 @@
      * @param view The view on which to invoke the corresponding method.
      * @param labeledId The labeled view id.
      */
-    public static void setLabelFor(View view, int labeledId) {
+    public static void setLabelFor(View view, @IdRes int labeledId) {
         IMPL.setLabelFor(view, labeledId);
     }
 
@@ -1039,6 +1243,7 @@
      * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
      * is lower than Jellybean MR1 (API 17)
      */
+    @ResolvedLayoutDirectionMode
     public static int getLayoutDirection(View view) {
         return IMPL.getLayoutDirection(view);
     }
@@ -1059,7 +1264,7 @@
      * proceeds up the parent chain of the view to get the value. If there is no parent, then it
      * will return the default {@link #LAYOUT_DIRECTION_LTR}.
      */
-    public static void setLayoutDirection(View view, int layoutDirection) {
+    public static void setLayoutDirection(View view, @LayoutDirectionMode int layoutDirection) {
         IMPL.setLayoutDirection(view, layoutDirection);
     }
 
@@ -1152,7 +1357,8 @@
      *
      * @see ViewCompat#setAccessibilityLiveRegion(View, int)
      */
-    public int getAccessibilityLiveRegion(View view) {
+    @AccessibilityLiveRegion
+    public static int getAccessibilityLiveRegion(View view) {
         return IMPL.getAccessibilityLiveRegion(view);
     }
 
@@ -1184,7 +1390,106 @@
      *        <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
      *        </ul>
      */
-    public void setAccessibilityLiveRegion(View view, int mode) {
+    public static void setAccessibilityLiveRegion(View view, @AccessibilityLiveRegion int mode) {
         IMPL.setAccessibilityLiveRegion(view, mode);
     }
+
+    /**
+     * Returns the start padding of the specified view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @param view The view to get padding for
+     * @return the start padding in pixels
+     */
+    public static int getPaddingStart(View view) {
+        return IMPL.getPaddingStart(view);
+    }
+
+    /**
+     * Returns the end padding of the specified view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @param view The view to get padding for
+     * @return the end padding in pixels
+     */
+    public static int getPaddingEnd(View view) {
+        return IMPL.getPaddingEnd(view);
+    }
+
+    /**
+     * Sets the relative padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingStart}, {@link View#getPaddingTop},
+     * {@link #getPaddingEnd} and {@link View#getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @param view The view on which to set relative padding
+     * @param start the start padding in pixels
+     * @param top the top padding in pixels
+     * @param end the end padding in pixels
+     * @param bottom the bottom padding in pixels
+     */
+    public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+        IMPL.setPaddingRelative(view, start, top, end, bottom);
+    }
+
+    /**
+     * Notify a view that it is being temporarily detached.
+     */
+    public static void dispatchStartTemporaryDetach(View view) {
+        IMPL.dispatchStartTemporaryDetach(view);
+    }
+
+    /**
+     * Notify a view that its temporary detach has ended; the view is now reattached.
+     */
+    public static void dispatchFinishTemporaryDetach(View view) {
+        IMPL.dispatchFinishTemporaryDetach(view);
+    }
+
+    /**
+     * The horizontal location of this view relative to its {@link View#getLeft() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The horizontal position of this view relative to its left position, in pixels.
+     */
+    public static float getTranslationX(View view) {
+        return IMPL.getTranslationX(view);
+    }
+
+    /**
+     * The vertical location of this view relative to its {@link View#getTop() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * @return The vertical position of this view relative to its top position, in pixels.
+     */
+    public static float getTranslationY(View view) {
+        return IMPL.getTranslationY(view);
+    }
+
+    /**
+     * Returns the minimum width of the view.
+     *
+     * <p>Prior to API 16 this will return 0.</p>
+     *
+     * @return the minimum width the view will try to be.
+     */
+    public static int getMinimumWidth(View view) {
+        return IMPL.getMinimumWidth(view);
+    }
+
+    /**
+     * Returns the minimum height of the view.
+     *
+     * <p>Prior to API 16 this will return 0.</p>
+     *
+     * @return the minimum height the view will try to be.
+     */
+    public static int getMinimumHeight(View view) {
+        return IMPL.getMinimumHeight(view);
+    }
 }
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index e90744c..e9fc1a8 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.support.annotation.DrawableRes;
 import android.support.v4.os.ParcelableCompat;
 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
@@ -740,7 +741,7 @@
      *
      * @param resId Resource ID of a drawable to display between pages
      */
-    public void setPageMarginDrawable(int resId) {
+    public void setPageMarginDrawable(@DrawableRes int resId) {
         setPageMarginDrawable(getContext().getResources().getDrawable(resId));
     }
 
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 92ef51d..e4ba5cf 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -27,6 +27,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.KeyEventCompat;
@@ -40,9 +43,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -73,6 +79,11 @@
 public class DrawerLayout extends ViewGroup {
     private static final String TAG = "DrawerLayout";
 
+    /** @hide */
+    @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {}
+
     /**
      * Indicates that any drawers are in an idle, settled state. No animation is in progress.
      */
@@ -88,6 +99,11 @@
      */
     public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
 
+    /** @hide */
+    @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LockMode {}
+
     /**
      * The drawer is unlocked.
      */
@@ -105,6 +121,12 @@
      */
     public static final int LOCK_MODE_LOCKED_OPEN = 2;
 
+    /** @hide */
+    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface EdgeGravity {}
+
+
     private static final int MIN_DRAWER_MARGIN = 64; // dp
 
     private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
@@ -132,6 +154,9 @@
             android.R.attr.layout_gravity
     };
 
+    private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
+            new ChildAccessibilityDelegate();
+
     private int mMinDrawerMargin;
 
     private int mScrimColor = DEFAULT_SCRIM_COLOR;
@@ -193,7 +218,7 @@
          *
          * @param newState The new drawer motion state
          */
-        public void onDrawerStateChanged(int newState);
+        public void onDrawerStateChanged(@State int newState);
     }
 
     /**
@@ -249,6 +274,9 @@
         // So that we can catch the back button
         setFocusableInTouchMode(true);
 
+        ViewCompat.setImportantForAccessibility(this,
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
         ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
         ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
     }
@@ -260,7 +288,7 @@
      * @param shadowDrawable Shadow drawable to use at the edge of a drawer
      * @param gravity Which drawer the shadow should apply to
      */
-    public void setDrawerShadow(Drawable shadowDrawable, int gravity) {
+    public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) {
         /*
          * TODO Someone someday might want to set more complex drawables here.
          * They're probably nuts, but we might want to consider registering callbacks,
@@ -286,7 +314,7 @@
      * @param resId Resource id of a shadow drawable to use at the edge of a drawer
      * @param gravity Which drawer the shadow should apply to
      */
-    public void setDrawerShadow(int resId, int gravity) {
+    public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) {
         setDrawerShadow(getResources().getDrawable(resId), gravity);
     }
 
@@ -323,7 +351,7 @@
      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
      */
-    public void setDrawerLockMode(int lockMode) {
+    public void setDrawerLockMode(@LockMode int lockMode) {
         setDrawerLockMode(lockMode, Gravity.LEFT);
         setDrawerLockMode(lockMode, Gravity.RIGHT);
     }
@@ -347,7 +375,7 @@
      * @see #LOCK_MODE_LOCKED_CLOSED
      * @see #LOCK_MODE_LOCKED_OPEN
      */
-    public void setDrawerLockMode(int lockMode, int edgeGravity) {
+    public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
                 ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -395,7 +423,7 @@
      * @see #LOCK_MODE_LOCKED_CLOSED
      * @see #LOCK_MODE_LOCKED_OPEN
      */
-    public void setDrawerLockMode(int lockMode, View drawerView) {
+    public void setDrawerLockMode(@LockMode int lockMode, View drawerView) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a " +
                     "drawer with appropriate layout_gravity");
@@ -411,7 +439,8 @@
      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
      *         {@link #LOCK_MODE_LOCKED_OPEN}.
      */
-    public int getDrawerLockMode(int edgeGravity) {
+    @LockMode
+    public int getDrawerLockMode(@EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -429,6 +458,7 @@
      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
      *         {@link #LOCK_MODE_LOCKED_OPEN}.
      */
+    @LockMode
     public int getDrawerLockMode(View drawerView) {
         final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
         if (absGravity == Gravity.LEFT) {
@@ -449,7 +479,7 @@
      *            drawer to set the title for.
      * @param title The title for the drawer.
      */
-    public void setDrawerTitle(int edgeGravity, CharSequence title) {
+    public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -467,7 +497,8 @@
      * @return The title of the drawer, or null if none set.
      * @see #setDrawerTitle(int, CharSequence)
      */
-    public CharSequence getDrawerTitle(int edgeGravity) {
+    @Nullable
+    public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -482,7 +513,7 @@
      * Resolve the shared state of all drawers from the component ViewDragHelpers.
      * Should be called whenever a ViewDragHelper's state changes.
      */
-    void updateDrawerState(int forGravity, int activeState, View activeDrawer) {
+    void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) {
         final int leftState = mLeftDragger.getViewDragState();
         final int rightState = mRightDragger.getViewDragState();
 
@@ -521,6 +552,16 @@
                 mListener.onDrawerClosed(drawerView);
             }
 
+            // If no drawer is opened, all drawers are not shown
+            // for accessibility and the content is shown.
+            View content = getChildAt(0);
+            if (content != null) {
+                ViewCompat.setImportantForAccessibility(content,
+                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            }
+            ViewCompat.setImportantForAccessibility(drawerView,
+                            ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+
             // Only send WINDOW_STATE_CHANGE if the host has window focus. This
             // may change if support for multiple foreground windows (e.g. IME)
             // improves.
@@ -540,7 +581,19 @@
             if (mListener != null) {
                 mListener.onDrawerOpened(drawerView);
             }
+
+            // If a drawer is opened, only it is shown for
+            // accessibility and the content is not shown.
+            View content = getChildAt(0);
+            if (content != null) {
+                ViewCompat.setImportantForAccessibility(content,
+                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            }
+            ViewCompat.setImportantForAccessibility(drawerView,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
             sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            drawerView.requestFocus();
         }
     }
 
@@ -627,7 +680,7 @@
      * @param gravity Absolute gravity value
      * @return LEFT or RIGHT as appropriate, or a hex string
      */
-    static String gravityToString(int gravity) {
+    static String gravityToString(@EdgeGravity int gravity) {
         if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
             return "LEFT";
         }
@@ -1093,7 +1146,7 @@
      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
-    public void openDrawer(int gravity) {
+    public void openDrawer(@EdgeGravity int gravity) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
@@ -1133,7 +1186,7 @@
      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
-    public void closeDrawer(int gravity) {
+    public void closeDrawer(@EdgeGravity int gravity) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
@@ -1168,7 +1221,7 @@
      * @param drawerGravity Gravity of the drawer to check
      * @return true if the given drawer view is in an open state
      */
-    public boolean isDrawerOpen(int drawerGravity) {
+    public boolean isDrawerOpen(@EdgeGravity int drawerGravity) {
         final View drawerView = findDrawerWithGravity(drawerGravity);
         if (drawerView != null) {
             return isDrawerOpen(drawerView);
@@ -1193,13 +1246,13 @@
 
     /**
      * Check if a given drawer view is currently visible on-screen. The drawer
-     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
+     * may be only peeking onto the screen, fully extended, or anywhere in between.
      * If there is no drawer with the given gravity this method will return false.
      *
      * @param drawerGravity Gravity of the drawer to check
      * @return true if the given drawer is visible on-screen
      */
-    public boolean isDrawerVisible(int drawerGravity) {
+    public boolean isDrawerVisible(@EdgeGravity int drawerGravity) {
         final View drawerView = findDrawerWithGravity(drawerGravity);
         if (drawerView != null) {
             return isDrawerVisible(drawerView);
@@ -1336,6 +1389,35 @@
         return ss;
     }
 
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        // Until a drawer is open, it is hidden from accessibility.
+        if (index > 0 || (index < 0 && getChildCount() > 0)) {
+            ViewCompat.setImportantForAccessibility(child,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            // Also set a delegate to break the child-parent relation if the
+            // child is hidden. For details (see incluceChildForAccessibility).
+            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
+        } else  {
+            // Initially, the content is shown for accessibility.
+            ViewCompat.setImportantForAccessibility(child,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+        super.addView(child, index, params);
+    }
+
+    private static boolean includeChildForAccessibilitiy(View child) {
+        // If the child is not important for accessibility we make
+        // sure this hides the entire subtree rooted at it as the
+        // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not
+        // supported on older platforms but we want to hide the entire
+        // content and not opened drawers if a drawer is opened.
+        return ViewCompat.getImportantForAccessibility(child)
+                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                    && ViewCompat.getImportantForAccessibility(child)
+                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
+    }
+
     /**
      * State persisted across instances
      */
@@ -1633,31 +1715,8 @@
             final int childCount = v.getChildCount();
             for (int i = 0; i < childCount; i++) {
                 final View child = v.getChildAt(i);
-                if (filter(child)) {
-                    continue;
-                }
-
-                // Adding children that are marked as not important for
-                // accessibility will break the hierarchy, so we need to check
-                // that value and re-parent views if necessary.
-                final int importance = ViewCompat.getImportantForAccessibility(child);
-                switch (importance) {
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS:
-                        // Always skip NO_HIDE views and their descendants.
-                        break;
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO:
-                        // Re-parent children of NO view groups, skip NO views.
-                        if (child instanceof ViewGroup) {
-                            addChildrenForAccessibility(info, (ViewGroup) child);
-                        }
-                        break;
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO:
-                        // Force AUTO views to YES and add them.
-                        ViewCompat.setImportantForAccessibility(
-                                child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES:
-                        info.addChild(child);
-                        break;
+                if (includeChildForAccessibilitiy(child)) {
+                    info.addChild(child);
                 }
             }
         }
@@ -1665,17 +1724,12 @@
         @Override
         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
                 AccessibilityEvent event) {
-            if (!filter(child)) {
+            if (includeChildForAccessibilitiy(child)) {
                 return super.onRequestSendAccessibilityEvent(host, child, event);
             }
             return false;
         }
 
-        public boolean filter(View child) {
-            final View openDrawer = findOpenDrawer();
-            return openDrawer != null && openDrawer != child;
-        }
-
         /**
          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
          * seem to be a few elements that are not easily cloneable using the underlying API.
@@ -1707,4 +1761,18 @@
             dest.addAction(src.getActions());
         }
     }
+
+    final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View child,
+                AccessibilityNodeInfoCompat info) {
+            super.onInitializeAccessibilityNodeInfo(child, info);
+            if (!includeChildForAccessibilitiy(child)) {
+                // If we are ignoring the sub-tree rooted at the child,
+                // break the connection to the rest of the node tree.
+                // For details refer to includeChildForAccessibilitiy.
+                info.setParent(null);
+            }
+        }
+    }
 }
diff --git a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
index 0b3b30d..b23ca23 100644
--- a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
+++ b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
@@ -59,6 +59,10 @@
     public boolean canTargetScrollVertically(int direction) {
         final ListView target = mTarget;
         final int itemCount = target.getCount();
+        if (itemCount == 0) {
+            return false;
+        }
+
         final int childCount = target.getChildCount();
         final int firstPosition = target.getFirstVisiblePosition();
         final int lastPosition = firstPosition + childCount;
diff --git a/v4/java/android/support/v4/widget/ScrollerCompat.java b/v4/java/android/support/v4/widget/ScrollerCompat.java
index fec045a..cf71e8e 100644
--- a/v4/java/android/support/v4/widget/ScrollerCompat.java
+++ b/v4/java/android/support/v4/widget/ScrollerCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Scroller;
 
@@ -29,7 +30,10 @@
  * the APIs from Scroller or OverScroller.</p>
  */
 public class ScrollerCompat {
+    private static final String TAG = "ScrollerCompat";
+
     Object mScroller;
+    ScrollerCompatImpl mImpl;
 
     interface ScrollerCompatImpl {
         Object createScroller(Context context, Interpolator interpolator);
@@ -52,7 +56,118 @@
         int getFinalY(Object scroller);
     }
 
+    static final int CHASE_FRAME_TIME = 16; // ms per target frame
+
+    static class Chaser {
+        private int mX;
+        private int mY;
+        private int mTargetX;
+        private int mTargetY;
+        private float mTranslateSmoothing = 2;
+        private long mLastTime;
+        private boolean mAborted;
+
+        @Override
+        public String toString() {
+            return "{x=" + mX + " y=" + mY + " targetX=" + mTargetX + " targetY=" + mTargetY +
+                    " smoothing=" + mTranslateSmoothing + " lastTime=" + mLastTime + "}";
+        }
+
+        public int getCurrX() {
+            return mX;
+        }
+
+        public int getCurrY() {
+            return mY;
+        }
+
+        public int getFinalX() {
+            return mTargetX;
+        }
+
+        public int getFinalY() {
+            return mTargetY;
+        }
+
+        public void setCurrentPosition(int x, int y) {
+            mX = x;
+            mY = y;
+            mAborted = false;
+        }
+
+        public void setSmoothing(float smoothing) {
+            if (smoothing < 0) {
+                throw new IllegalArgumentException("smoothing value must be positive");
+            }
+            mTranslateSmoothing = smoothing;
+        }
+
+        public boolean isSmoothingEnabled() {
+            return mTranslateSmoothing > 0;
+        }
+
+        public void setTarget(int targetX, int targetY) {
+            mTargetX = targetX;
+            mTargetY = targetY;
+        }
+
+        public void abort() {
+            mX = mTargetX;
+            mY = mTargetY;
+            mLastTime = AnimationUtils.currentAnimationTimeMillis();
+            mAborted = true;
+        }
+
+        public boolean isFinished() {
+            return mAborted || (mX == mTargetX && mY == mTargetY);
+        }
+
+        public boolean computeScrollOffset() {
+            if (isSmoothingEnabled() && !isFinished()) {
+                final long now = AnimationUtils.currentAnimationTimeMillis();
+                final long dt = now - mLastTime;
+                final float framesElapsed = (float) dt / CHASE_FRAME_TIME;
+
+                if (framesElapsed > 0) {
+                    for (int i = 0; i < framesElapsed; i++) {
+                        final int totalDx = mTargetX - mX;
+                        final int totalDy = mTargetY - mY;
+
+                        final int dx = (int) (totalDx / mTranslateSmoothing);
+                        final int dy = (int) (totalDy / mTranslateSmoothing);
+
+                        mX += dx;
+                        mY += dy;
+
+                        // Handle cropping at the end
+                        if (mX != mTargetX && dx == 0) {
+                            mX = mTargetX;
+                        }
+                        if (mY != mTargetY && dy == 0) {
+                            mY = mTargetY;
+                        }
+                    }
+
+                    mLastTime = now;
+                }
+                return true;
+            }
+            return false;
+        }
+    }
+
     static class ScrollerCompatImplBase implements ScrollerCompatImpl {
+        private Chaser mChaser;
+
+        public ScrollerCompatImplBase() {
+            mChaser = createChaser();
+        }
+
+        protected Chaser createChaser() {
+            // Override if running on a platform version where this isn't needed
+            return new Chaser();
+        }
+
         @Override
         public Object createScroller(Context context, Interpolator interpolator) {
             return interpolator != null ?
@@ -61,16 +176,23 @@
 
         @Override
         public boolean isFinished(Object scroller) {
-            return ((Scroller) scroller).isFinished();
+            return (!isSmoothingEnabled() || mChaser.isFinished()) &&
+                    ((Scroller) scroller).isFinished();
         }
 
         @Override
         public int getCurrX(Object scroller) {
+            if (isSmoothingEnabled()) {
+                return mChaser.getCurrX();
+            }
             return ((Scroller) scroller).getCurrX();
         }
 
         @Override
         public int getCurrY(Object scroller) {
+            if (isSmoothingEnabled()) {
+                return mChaser.getCurrY();
+            }
             return ((Scroller) scroller).getCurrY();
         }
 
@@ -81,34 +203,65 @@
 
         @Override
         public boolean computeScrollOffset(Object scroller) {
-            return ((Scroller) scroller).computeScrollOffset();
+            final Scroller s = (Scroller) scroller;
+            final boolean result = s.computeScrollOffset();
+            if (isSmoothingEnabled()) {
+                mChaser.setTarget(s.getCurrX(), s.getCurrY());
+                if (isSmoothingEnabled() && !mChaser.isFinished()) {
+                    return mChaser.computeScrollOffset() || result;
+                }
+            }
+            return result;
+        }
+
+        private boolean isSmoothingEnabled() {
+            return mChaser != null && mChaser.isSmoothingEnabled();
         }
 
         @Override
         public void startScroll(Object scroller, int startX, int startY, int dx, int dy) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ((Scroller) scroller).startScroll(startX, startY, dx, dy);
         }
 
         @Override
         public void startScroll(Object scroller, int startX, int startY, int dx, int dy,
                 int duration) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ((Scroller) scroller).startScroll(startX, startY, dx, dy, duration);
         }
 
         @Override
         public void fling(Object scroller, int startX, int startY, int velX, int velY,
                 int minX, int maxX, int minY, int maxY) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY);
         }
 
         @Override
         public void fling(Object scroller, int startX, int startY, int velX, int velY,
                 int minX, int maxX, int minY, int maxY, int overX, int overY) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY);
         }
 
         @Override
         public void abortAnimation(Object scroller) {
+            if (mChaser != null) {
+                mChaser.abort();
+            }
             ((Scroller) scroller).abortAnimation();
         }
 
@@ -141,23 +294,40 @@
     }
 
     static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl {
+        private Chaser mChaser;
+
+        public ScrollerCompatImplGingerbread() {
+            mChaser = createChaser();
+        }
+
         @Override
         public Object createScroller(Context context, Interpolator interpolator) {
             return ScrollerCompatGingerbread.createScroller(context, interpolator);
         }
 
+        protected Chaser createChaser() {
+            return new Chaser();
+        }
+
         @Override
         public boolean isFinished(Object scroller) {
-            return ScrollerCompatGingerbread.isFinished(scroller);
+            return (!isSmoothingEnabled() || mChaser.isFinished()) &&
+                    ScrollerCompatGingerbread.isFinished(scroller);
         }
 
         @Override
         public int getCurrX(Object scroller) {
+            if (isSmoothingEnabled()) {
+                return mChaser.getCurrX();
+            }
             return ScrollerCompatGingerbread.getCurrX(scroller);
         }
 
         @Override
         public int getCurrY(Object scroller) {
+            if (isSmoothingEnabled()) {
+                return mChaser.getCurrY();
+            }
             return ScrollerCompatGingerbread.getCurrY(scroller);
         }
 
@@ -168,23 +338,47 @@
 
         @Override
         public boolean computeScrollOffset(Object scroller) {
-            return ScrollerCompatGingerbread.computeScrollOffset(scroller);
+            final boolean result = ScrollerCompatGingerbread.computeScrollOffset(scroller);
+            if (isSmoothingEnabled()) {
+                mChaser.setTarget(ScrollerCompatGingerbread.getCurrX(scroller),
+                        ScrollerCompatGingerbread.getCurrY(scroller));
+                if (!mChaser.isFinished()) {
+                    return mChaser.computeScrollOffset() || result;
+                }
+            }
+            return result;
+        }
+
+        private boolean isSmoothingEnabled() {
+            return mChaser != null && mChaser.isSmoothingEnabled();
         }
 
         @Override
         public void startScroll(Object scroller, int startX, int startY, int dx, int dy) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy);
         }
 
         @Override
         public void startScroll(Object scroller, int startX, int startY, int dx, int dy,
                 int duration) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy, duration);
         }
 
         @Override
         public void fling(Object scroller, int startX, int startY, int velX, int velY,
                 int minX, int maxX, int minY, int maxY) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY,
                     minX, maxX, minY, maxY);
         }
@@ -192,12 +386,19 @@
         @Override
         public void fling(Object scroller, int startX, int startY, int velX, int velY,
                 int minX, int maxX, int minY, int maxY, int overX, int overY) {
+            if (isSmoothingEnabled()) {
+                mChaser.abort();
+                mChaser.setCurrentPosition(startX, startY);
+            }
             ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY,
                     minX, maxX, minY, maxY, overX, overY);
         }
 
         @Override
         public void abortAnimation(Object scroller) {
+            if (mChaser != null) {
+                mChaser.abort();
+            }
             ScrollerCompatGingerbread.abortAnimation(scroller);
         }
 
@@ -235,18 +436,6 @@
         }
     }
 
-    static final ScrollerCompatImpl IMPL;
-    static {
-        final int version = Build.VERSION.SDK_INT;
-        if (version >= 14) { // ICS
-            IMPL = new ScrollerCompatImplIcs();
-        } else if (version >= 9) { // Gingerbread
-            IMPL = new ScrollerCompatImplGingerbread();
-        } else {
-            IMPL = new ScrollerCompatImplBase();
-        }
-    }
-
     public static ScrollerCompat create(Context context) {
         return create(context, null);
     }
@@ -256,7 +445,23 @@
     }
 
     ScrollerCompat(Context context, Interpolator interpolator) {
-        mScroller = IMPL.createScroller(context, interpolator);
+        this(Build.VERSION.SDK_INT, context, interpolator);
+
+    }
+
+    /**
+     * Private constructer where API version can be provided.
+     * Useful for unit testing.
+     */
+    private ScrollerCompat(int apiVersion, Context context, Interpolator interpolator) {
+        if (apiVersion >= 14) { // ICS
+            mImpl = new ScrollerCompatImplIcs();
+        } else if (apiVersion>= 9) { // Gingerbread
+            mImpl = new ScrollerCompatImplGingerbread();
+        } else {
+            mImpl = new ScrollerCompatImplBase();
+        }
+        mScroller = mImpl.createScroller(context, interpolator);
     }
 
     /**
@@ -265,7 +470,7 @@
      * @return True if the scroller has finished scrolling, false otherwise.
      */
     public boolean isFinished() {
-        return IMPL.isFinished(mScroller);
+        return mImpl.isFinished(mScroller);
     }
 
     /**
@@ -274,7 +479,7 @@
      * @return The new X offset as an absolute distance from the origin.
      */
     public int getCurrX() {
-        return IMPL.getCurrX(mScroller);
+        return mImpl.getCurrX(mScroller);
     }
 
     /**
@@ -283,21 +488,21 @@
      * @return The new Y offset as an absolute distance from the origin.
      */
     public int getCurrY() {
-        return IMPL.getCurrY(mScroller);
+        return mImpl.getCurrY(mScroller);
     }
 
     /**
      * @return The final X position for the scroll in progress, if known.
      */
     public int getFinalX() {
-        return IMPL.getFinalX(mScroller);
+        return mImpl.getFinalX(mScroller);
     }
 
     /**
      * @return The final Y position for the scroll in progress, if known.
      */
     public int getFinalY() {
-        return IMPL.getFinalY(mScroller);
+        return mImpl.getFinalY(mScroller);
     }
 
     /**
@@ -311,7 +516,7 @@
      * negative.
      */
     public float getCurrVelocity() {
-        return IMPL.getCurrVelocity(mScroller);
+        return mImpl.getCurrVelocity(mScroller);
     }
 
     /**
@@ -320,7 +525,7 @@
      * new location.
      */
     public boolean computeScrollOffset() {
-        return IMPL.computeScrollOffset(mScroller);
+        return mImpl.computeScrollOffset(mScroller);
     }
 
     /**
@@ -338,7 +543,7 @@
      *        content up.
      */
     public void startScroll(int startX, int startY, int dx, int dy) {
-        IMPL.startScroll(mScroller, startX, startY, dx, dy);
+        mImpl.startScroll(mScroller, startX, startY, dx, dy);
     }
 
     /**
@@ -355,7 +560,7 @@
      * @param duration Duration of the scroll in milliseconds.
      */
     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
-        IMPL.startScroll(mScroller, startX, startY, dx, dy, duration);
+        mImpl.startScroll(mScroller, startX, startY, dx, dy, duration);
     }
 
     /**
@@ -379,7 +584,7 @@
      */
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY) {
-        IMPL.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
+        mImpl.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
     }
 
     /**
@@ -407,7 +612,7 @@
      */
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY, int overX, int overY) {
-        IMPL.fling(mScroller, startX, startY, velocityX, velocityY,
+        mImpl.fling(mScroller, startX, startY, velocityX, velocityY,
                 minX, maxX, minY, maxY, overX, overY);
     }
 
@@ -416,7 +621,7 @@
      * position.
      */
     public void abortAnimation() {
-        IMPL.abortAnimation(mScroller);
+        mImpl.abortAnimation(mScroller);
     }
 
 
@@ -434,7 +639,7 @@
      *              desired distance from finalX. Absolute value - must be positive.
      */
     public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
-        IMPL.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX);
+        mImpl.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX);
     }
 
     /**
@@ -451,7 +656,7 @@
      *              desired distance from finalY. Absolute value - must be positive.
      */
     public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
-        IMPL.notifyVerticalEdgeReached(mScroller, startY, finalY, overY);
+        mImpl.notifyVerticalEdgeReached(mScroller, startY, finalY, overY);
     }
 
     /**
@@ -468,6 +673,6 @@
      *         interpolating back to a valid value.
      */
     public boolean isOverScrolled() {
-        return IMPL.isOverScrolled(mScroller);
+        return mImpl.isOverScrolled(mScroller);
     }
 }
diff --git a/v4/java/android/support/v4/widget/SlidingPaneLayout.java b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
index 1c23994..391ba99 100644
--- a/v4/java/android/support/v4/widget/SlidingPaneLayout.java
+++ b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
@@ -29,6 +29,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.DrawableRes;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
@@ -117,9 +118,14 @@
     private int mCoveredFadeColor;
 
     /**
-     * Drawable used to draw the shadow between panes.
+     * Drawable used to draw the shadow between panes by default.
      */
-    private Drawable mShadowDrawable;
+    private Drawable mShadowDrawableLeft;
+
+    /**
+     * Drawable used to draw the shadow between panes to support RTL (right to left language).
+     */
+    private Drawable mShadowDrawableRight;
 
     /**
      * The size of the overhang in pixels.
@@ -262,7 +268,6 @@
         ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
-        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
         mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
     }
 
@@ -345,8 +350,11 @@
     }
 
     void updateObscuredViewsVisibility(View panel) {
-        final int leftBound = getPaddingLeft();
-        final int rightBound = getWidth() - getPaddingRight();
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        final int startBound = isLayoutRtl ? (getWidth() - getPaddingRight()) :
+            getPaddingLeft();
+        final int endBound = isLayoutRtl ? getPaddingLeft() :
+            (getWidth() - getPaddingRight());
         final int topBound = getPaddingTop();
         final int bottomBound = getHeight() - getPaddingBottom();
         final int left;
@@ -370,9 +378,11 @@
                 break;
             }
 
-            final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+            final int clampedChildLeft = Math.max((isLayoutRtl ? endBound :
+                startBound), child.getLeft());
             final int clampedChildTop = Math.max(topBound, child.getTop());
-            final int clampedChildRight = Math.min(rightBound, child.getRight());
+            final int clampedChildRight = Math.min((isLayoutRtl ? startBound :
+                endBound), child.getRight());
             final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
             final int vis;
             if (clampedChildLeft >= left && clampedChildTop >= top &&
@@ -641,14 +651,19 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        if (isLayoutRtl) {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
+        } else {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
+        }
         final int width = r - l;
-        final int paddingLeft = getPaddingLeft();
-        final int paddingRight = getPaddingRight();
+        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
+        final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
         final int paddingTop = getPaddingTop();
 
         final int childCount = getChildCount();
-        int xStart = paddingLeft;
+        int xStart = paddingStart;
         int nextXStart = xStart;
 
         if (mFirstLayout) {
@@ -670,12 +685,13 @@
             if (lp.slideable) {
                 final int margin = lp.leftMargin + lp.rightMargin;
                 final int range = Math.min(nextXStart,
-                        width - paddingRight - mOverhangSize) - xStart - margin;
+                        width - paddingEnd - mOverhangSize) - xStart - margin;
                 mSlideRange = range;
-                lp.dimWhenOffset = xStart + lp.leftMargin + range + childWidth / 2 >
-                        width - paddingRight;
+                final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
+                lp.dimWhenOffset = xStart + lpMargin + range + childWidth / 2 >
+                        width - paddingEnd;
                 final int pos = (int) (range * mSlideOffset);
-                xStart += pos + lp.leftMargin;
+                xStart += pos + lpMargin;
                 mSlideOffset = (float) pos / mSlideRange;
             } else if (mCanSlide && mParallaxBy != 0) {
                 offset = (int) ((1 - mSlideOffset) * mParallaxBy);
@@ -684,8 +700,16 @@
                 xStart = nextXStart;
             }
 
-            final int childLeft = xStart - offset;
-            final int childRight = childLeft + childWidth;
+            final int childRight;
+            final int childLeft;
+            if (isLayoutRtl) {
+                childRight = width - xStart + offset;
+                childLeft = childRight - childWidth;
+            } else {
+                childLeft = xStart - offset;
+                childRight = childLeft + childWidth;
+            }
+
             final int childTop = paddingTop;
             final int childBottom = childTop + child.getMeasuredHeight();
             child.layout(childLeft, paddingTop, childRight, childBottom);
@@ -918,11 +942,17 @@
             mSlideOffset = 0;
             return;
         }
-
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-        final int leftBound = getPaddingLeft() + lp.leftMargin;
 
-        mSlideOffset = (float) (newLeft - leftBound) / mSlideRange;
+        int childWidth = mSlideableView.getWidth();
+        final int newStart = isLayoutRtl ? getWidth() - newLeft - childWidth : newLeft;
+
+        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
+        final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
+        final int startBound = paddingStart + lpMargin;
+
+        mSlideOffset = (float) (newStart - startBound) / mSlideRange;
 
         if (mParallaxBy != 0) {
             parallaxOtherViews(mSlideOffset);
@@ -968,7 +998,11 @@
         if (mCanSlide && !lp.slideable && mSlideableView != null) {
             // Clip against the slider; no sense drawing what will immediately be covered.
             canvas.getClipBounds(mTmpRect);
-            mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
+            if (isLayoutRtlSupport()) {
+                mTmpRect.left = Math.max(mTmpRect.left, mSlideableView.getRight());
+            } else {
+                mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
+            }
             canvas.clipRect(mTmpRect);
         }
 
@@ -1016,10 +1050,18 @@
             return false;
         }
 
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
 
-        final int leftBound = getPaddingLeft() + lp.leftMargin;
-        int x = (int) (leftBound + slideOffset * mSlideRange);
+        int x;
+        if (isLayoutRtl) {
+            int startBound = getPaddingRight() + lp.rightMargin;
+            int childWidth = mSlideableView.getWidth();
+            x = (int) (getWidth() - (startBound + slideOffset * mSlideRange + childWidth));
+        } else {
+            int startBound = getPaddingLeft() + lp.leftMargin;
+            x = (int) (startBound + slideOffset * mSlideRange);
+        }
 
         if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) {
             setAllChildrenVisible();
@@ -1042,13 +1084,35 @@
     }
 
     /**
+     * @deprecated Renamed to {@link #setShadowDrawableLeft(Drawable d)} to support LTR (left to
+     * right language) and {@link #setShadowDrawableRight(Drawable d)} to support RTL (right to left
+     * language) during opening/closing.
+     *
+     * @param d drawable to use as a shadow
+     */
+    @Deprecated
+    public void setShadowDrawable(Drawable d) {
+        setShadowDrawableLeft(d);
+    }
+
+    /**
      * Set a drawable to use as a shadow cast by the right pane onto the left pane
      * during opening/closing.
      *
      * @param d drawable to use as a shadow
      */
-    public void setShadowDrawable(Drawable d) {
-        mShadowDrawable = d;
+    public void setShadowDrawableLeft(Drawable d) {
+        mShadowDrawableLeft = d;
+    }
+
+    /**
+     * Set a drawable to use as a shadow cast by the left pane onto the right pane
+     * during opening/closing to support right to left language.
+     *
+     * @param d drawable to use as a shadow
+     */
+    public void setShadowDrawableRight(Drawable d) {
+        mShadowDrawableRight = d;
     }
 
     /**
@@ -1057,32 +1121,72 @@
      *
      * @param resId Resource ID of a drawable to use
      */
-    public void setShadowResource(int resId) {
+    @Deprecated
+    public void setShadowResource(@DrawableRes int resId) {
         setShadowDrawable(getResources().getDrawable(resId));
     }
 
+    /**
+     * Set a drawable to use as a shadow cast by the right pane onto the left pane
+     * during opening/closing.
+     *
+     * @param resId Resource ID of a drawable to use
+     */
+    public void setShadowResourceLeft(int resId) {
+        setShadowDrawableLeft(getResources().getDrawable(resId));
+    }
+
+    /**
+     * Set a drawable to use as a shadow cast by the left pane onto the right pane
+     * during opening/closing to support right to left language.
+     *
+     * @param resId Resource ID of a drawable to use
+     */
+    public void setShadowResourceRight(int resId) {
+        setShadowDrawableRight(getResources().getDrawable(resId));
+    }
+
+
     @Override
     public void draw(Canvas c) {
         super.draw(c);
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        Drawable shadowDrawable;
+        if (isLayoutRtl) {
+            shadowDrawable = mShadowDrawableRight;
+        } else {
+            shadowDrawable = mShadowDrawableLeft;
+        }
 
         final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
-        if (shadowView == null || mShadowDrawable == null) {
+        if (shadowView == null || shadowDrawable == null) {
             // No need to draw a shadow if we don't have one.
             return;
         }
 
-        final int shadowWidth = mShadowDrawable.getIntrinsicWidth();
-        final int right = shadowView.getLeft();
         final int top = shadowView.getTop();
         final int bottom = shadowView.getBottom();
-        final int left = right - shadowWidth;
-        mShadowDrawable.setBounds(left, top, right, bottom);
-        mShadowDrawable.draw(c);
+
+        final int shadowWidth = shadowDrawable.getIntrinsicWidth();
+        final int left;
+        final int right;
+        if (isLayoutRtlSupport()) {
+            left = shadowView.getRight();
+            right = left + shadowWidth;
+        } else {
+            right = shadowView.getLeft();
+            left = right - shadowWidth;
+        }
+
+        shadowDrawable.setBounds(left, top, right, bottom);
+        shadowDrawable.draw(c);
     }
 
     private void parallaxOtherViews(float slideOffset) {
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
-        final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0;
+        final boolean dimViews = slideLp.dimWhenOffset &&
+                (isLayoutRtl ? slideLp.rightMargin : slideLp.leftMargin) <= 0;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final View v = getChildAt(i);
@@ -1093,10 +1197,11 @@
             final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
             final int dx = oldOffset - newOffset;
 
-            v.offsetLeftAndRight(dx);
+            v.offsetLeftAndRight(isLayoutRtl ? -dx : dx);
 
             if (dimViews) {
-                dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor);
+                dimChildView(v, isLayoutRtl ? mParallaxOffset - 1 :
+                    1 - mParallaxOffset, mCoveredFadeColor);
             }
         }
     }
@@ -1132,7 +1237,7 @@
             }
         }
 
-        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+        return checkV && ViewCompat.canScrollHorizontally(v, (isLayoutRtlSupport() ? dx : -dx));
     }
 
     boolean isDimmed(View child) {
@@ -1228,9 +1333,20 @@
         @Override
         public void onViewReleased(View releasedChild, float xvel, float yvel) {
             final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
-            int left = getPaddingLeft() + lp.leftMargin;
-            if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
-                left += mSlideRange;
+
+            int left;
+            if (isLayoutRtlSupport()) {
+                int startToRight =  getPaddingRight() + lp.rightMargin;
+                if (xvel < 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
+                    startToRight += mSlideRange;
+                }
+                int childWidth = mSlideableView.getWidth();
+                left = getWidth() - startToRight - childWidth;
+            } else {
+                left = getPaddingLeft() + lp.leftMargin;
+                if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
+                    left += mSlideRange;
+                }
             }
             mDragHelper.settleCapturedViewAt(left, releasedChild.getTop());
             invalidate();
@@ -1244,11 +1360,18 @@
         @Override
         public int clampViewPositionHorizontal(View child, int left, int dx) {
             final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-            final int leftBound = getPaddingLeft() + lp.leftMargin;
-            final int rightBound = leftBound + mSlideRange;
 
-            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
-
+            final int newLeft;
+            if (isLayoutRtlSupport()) {
+                int startBound = getWidth() -
+                        (getPaddingRight() + lp.rightMargin + mSlideableView.getWidth());
+                int endBound =  startBound - mSlideRange;
+                newLeft = Math.max(Math.min(left, startBound), endBound);
+            } else {
+                int startBound = getPaddingLeft() + lp.leftMargin;
+                int endBound = startBound + mSlideRange;
+                newLeft = Math.min(Math.max(left, startBound), endBound);
+            }
             return newLeft;
         }
 
@@ -1514,4 +1637,8 @@
             mPostedRunnables.remove(this);
         }
     }
+
+    private boolean isLayoutRtlSupport() {
+        return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
 }
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index ca68acd..725a418 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -20,9 +20,11 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -56,28 +58,35 @@
  * refresh of the content wherever this gesture is used.</p>
  */
 public class SwipeRefreshLayout extends ViewGroup {
+    private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
+
     private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300;
     private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f;
     private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
     private static final float PROGRESS_BAR_HEIGHT = 4;
     private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;
     private static final int REFRESH_TRIGGER_DISTANCE = 120;
+    private static final int INVALID_POINTER = -1;
 
     private SwipeProgressBar mProgressBar; //the thing that shows progress is going
     private View mTarget; //the content that gets pulled down
     private int mOriginalOffsetTop;
     private OnRefreshListener mListener;
-    private MotionEvent mDownEvent;
     private int mFrom;
     private boolean mRefreshing = false;
     private int mTouchSlop;
     private float mDistanceToTriggerSync = -1;
-    private float mPrevY;
     private int mMediumAnimationDuration;
     private float mFromPercentage = 0;
     private float mCurrPercentage = 0;
     private int mProgressBarHeight;
     private int mCurrentTargetOffsetTop;
+
+    private float mInitialMotionY;
+    private float mLastMotionY;
+    private boolean mIsBeingDragged;
+    private int mActivePointerId = INVALID_POINTER;
+
     // Target is returning to its start offset because it was cancelled or a
     // refresh was triggered.
     private boolean mReturningToStart;
@@ -255,23 +264,33 @@
     }
 
     /**
+     * @deprecated Use {@link #setColorSchemeResources(int, int, int, int)}
+     */
+    @Deprecated
+    public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
+        setColorSchemeResources(colorRes1, colorRes2, colorRes3, colorRes4);
+    }
+
+    /**
+     * Set the four colors used in the progress animation from color resources.
+     * The first color will also be the color of the bar that grows in response
+     * to a user swipe gesture.
+     */
+    public void setColorSchemeResources(int colorRes1, int colorRes2, int colorRes3,
+            int colorRes4) {
+        final Resources res = getResources();
+        setColorSchemeColors(res.getColor(colorRes1), res.getColor(colorRes2),
+                res.getColor(colorRes3), res.getColor(colorRes4));
+    }
+
+    /**
      * Set the four colors used in the progress animation. The first color will
      * also be the color of the bar that grows in response to a user swipe
      * gesture.
-     *
-     * @param colorRes1 Color resource.
-     * @param colorRes2 Color resource.
-     * @param colorRes3 Color resource.
-     * @param colorRes4 Color resource.
      */
-    public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
+    public void setColorSchemeColors(int color1, int color2, int color3, int color4) {
         ensureTarget();
-        final Resources res = getResources();
-        final int color1 = res.getColor(colorRes1);
-        final int color2 = res.getColor(colorRes2);
-        final int color3 = res.getColor(colorRes3);
-        final int color4 = res.getColor(colorRes4);
-        mProgressBar.setColorScheme(color1, color2, color3,color4);
+        mProgressBar.setColorScheme(color1, color2, color3, color4);
     }
 
     /**
@@ -363,14 +382,59 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         ensureTarget();
-        boolean handled = false;
-        if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
+
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
             mReturningToStart = false;
         }
-        if (isEnabled() && !mReturningToStart && !canChildScrollUp()) {
-            handled = onTouchEvent(ev);
+
+        if (!isEnabled() || mReturningToStart || canChildScrollUp()) {
+            // Fail fast if we're not in a state where a swipe is possible
+            return false;
         }
-        return !handled ? super.onInterceptTouchEvent(ev) : handled;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mLastMotionY = mInitialMotionY = ev.getY();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mIsBeingDragged = false;
+                mCurrPercentage = 0;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mActivePointerId == INVALID_POINTER) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
+                    return false;
+                }
+
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (pointerIndex < 0) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
+                    return false;
+                }
+
+                final float y = MotionEventCompat.getY(ev, pointerIndex);
+                final float yDiff = y - mInitialMotionY;
+                if (yDiff > mTouchSlop) {
+                    mLastMotionY = y;
+                    mIsBeingDragged = true;
+                }
+                break;
+
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mIsBeingDragged = false;
+                mCurrPercentage = 0;
+                mActivePointerId = INVALID_POINTER;
+                break;
+        }
+
+        return mIsBeingDragged;
     }
 
     @Override
@@ -379,59 +443,84 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final int action = event.getAction();
-        boolean handled = false;
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
+            mReturningToStart = false;
+        }
+
+        if (!isEnabled() || mReturningToStart || canChildScrollUp()) {
+            // Fail fast if we're not in a state where a swipe is possible
+            return false;
+        }
+
         switch (action) {
             case MotionEvent.ACTION_DOWN:
+                mLastMotionY = mInitialMotionY = ev.getY();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mIsBeingDragged = false;
                 mCurrPercentage = 0;
-                mDownEvent = MotionEvent.obtain(event);
-                mPrevY = mDownEvent.getY();
                 break;
+
             case MotionEvent.ACTION_MOVE:
-                if (mDownEvent != null && !mReturningToStart) {
-                    final float eventY = event.getY();
-                    float yDiff = eventY - mDownEvent.getY();
-                    if (yDiff > mTouchSlop) {
-                        // User velocity passed min velocity; trigger a refresh
-                        if (yDiff > mDistanceToTriggerSync) {
-                            // User movement passed distance; trigger a refresh
-                            startRefresh();
-                            handled = true;
-                            break;
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (pointerIndex < 0) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
+                    return false;
+                }
+
+                final float y = MotionEventCompat.getY(ev, pointerIndex);
+                final float yDiff = y - mInitialMotionY;
+
+                if (!mIsBeingDragged && yDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                }
+
+                if (mIsBeingDragged) {
+                    // User velocity passed min velocity; trigger a refresh
+                    if (yDiff > mDistanceToTriggerSync) {
+                        // User movement passed distance; trigger a refresh
+                        startRefresh();
+                    } else {
+                        // Just track the user's movement
+                        setTriggerPercentage(
+                                mAccelerateInterpolator.getInterpolation(
+                                        yDiff / mDistanceToTriggerSync));
+                        updateContentOffsetTop((int) (yDiff));
+                        if (mLastMotionY > y && mTarget.getTop() == getPaddingTop()) {
+                            // If the user puts the view back at the top, we
+                            // don't need to. This shouldn't be considered
+                            // cancelling the gesture as the user can restart from the top.
+                            removeCallbacks(mCancel);
                         } else {
-                            // Just track the user's movement
-                            setTriggerPercentage(
-                                    mAccelerateInterpolator.getInterpolation(
-                                            yDiff / mDistanceToTriggerSync));
-                            float offsetTop = yDiff;
-                            if (mPrevY > eventY) {
-                                offsetTop = yDiff - mTouchSlop;
-                            }
-                            updateContentOffsetTop((int) (offsetTop));
-                            if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
-                                // If the user puts the view back at the top, we
-                                // don't need to. This shouldn't be considered
-                                // cancelling the gesture as the user can restart from the top.
-                                removeCallbacks(mCancel);
-                            } else {
-                                updatePositionTimeout();
-                            }
-                            mPrevY = event.getY();
-                            handled = true;
+                            updatePositionTimeout();
                         }
                     }
+                    mLastMotionY = y;
                 }
                 break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                final int index = MotionEventCompat.getActionIndex(ev);
+                mLastMotionY = MotionEventCompat.getY(ev, index);
+                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+                break;
+            }
+
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mDownEvent != null) {
-                    mDownEvent.recycle();
-                    mDownEvent = null;
-                }
-                break;
+                mIsBeingDragged = false;
+                mCurrPercentage = 0;
+                mActivePointerId = INVALID_POINTER;
+                return false;
         }
-        return handled;
+
+        return true;
     }
 
     private void startRefresh() {
@@ -461,6 +550,18 @@
         postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT);
     }
 
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
+            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+        }
+    }
+
     /**
      * Classes that wish to be notified when the swipe gesture correctly
      * triggers a refresh should implement this interface.
@@ -486,4 +587,4 @@
         public void onAnimationRepeat(Animation animation) {
         }
     }
-}
\ No newline at end of file
+}
diff --git a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
index be7192d..63552e4 100644
--- a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
+++ b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
@@ -43,4 +43,16 @@
     public static void setLayoutDirection(View view, int layoutDirection) {
         view.setLayoutDirection(layoutDirection);
     }
+
+    public static int getPaddingStart(View view) {
+        return view.getPaddingStart();
+    }
+
+    public static int getPaddingEnd(View view) {
+        return view.getPaddingEnd();
+    }
+
+    public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+        view.setPaddingRelative(start, top, end, bottom);
+    }
 }
diff --git a/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.java b/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.java
new file mode 100644
index 0000000..656dc43
--- /dev/null
+++ b/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.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.app;
+
+import android.app.PendingIntent;
+import android.graphics.Bitmap;
+
+/**
+ * Interface implemented by notification compat builders that support adding actions.
+ */
+interface NotificationBuilderWithActions {
+    public void addAction(int icon, CharSequence title, PendingIntent intent);
+}
diff --git a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
index 8fa7e98..b5968a5 100644
--- a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
+++ b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
@@ -20,75 +20,126 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.util.Log;
 import android.widget.RemoteViews;
+
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 
 class NotificationCompatJellybean {
-    private Notification.Builder b;
-    public NotificationCompatJellybean(Context context, Notification n,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            RemoteViews tickerView, int number,
-            PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
-            int mProgressMax, int mProgress, boolean mProgressIndeterminate,
-            boolean useChronometer, int priority, CharSequence subText) {
-        b = new Notification.Builder(context)
-            .setWhen(n.when)
-            .setSmallIcon(n.icon, n.iconLevel)
-            .setContent(n.contentView)
-            .setTicker(n.tickerText, tickerView)
-            .setSound(n.sound, n.audioStreamType)
-            .setVibrate(n.vibrate)
-            .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
-            .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
-            .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
-            .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
-            .setDefaults(n.defaults)
-            .setContentTitle(contentTitle)
-            .setContentText(contentText)
-            .setSubText(subText)
-            .setContentInfo(contentInfo)
-            .setContentIntent(contentIntent)
-            .setDeleteIntent(n.deleteIntent)
-            .setFullScreenIntent(fullScreenIntent,
-                    (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
-            .setLargeIcon(largeIcon)
-            .setNumber(number)
-            .setUsesChronometer(useChronometer)
-            .setPriority(priority)
-            .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+    public static final String TAG = "NotificationCompat";
+
+    /** Extras key used for Jellybean SDK and below. */
+    static final String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+
+    private static final Object sExtrasLock = new Object();
+    private static Field sExtrasField;
+    private static boolean sExtrasFieldAccessFailed;
+
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+        private final boolean mLocalOnly;
+        private final Bundle mExtras;
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int mProgressMax, int mProgress, boolean mProgressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                Bundle extras) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+            mLocalOnly = localOnly;
+            mExtras = extras;
+        }
+
+        @Override
+        public void addAction(int icon, CharSequence title, PendingIntent intent) {
+            b.addAction(icon, title, intent);
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            Notification notif = b.build();
+            if (mExtras != null) {
+                // Merge in developer provided extras, but let the values already set
+                // for keys take precedence.
+                Bundle extras = getExtras(notif);
+                Bundle mergeBundle = new Bundle(mExtras);
+                for (String key : mExtras.keySet()) {
+                    if (extras.containsKey(key)) {
+                        mergeBundle.remove(key);
+                    }
+                }
+                extras.putAll(mergeBundle);
+            }
+            if (mLocalOnly) {
+                getExtras(notif).putBoolean(EXTRA_LOCAL_ONLY, mLocalOnly);
+            }
+            return notif;
+        }
     }
 
-    public void addAction(int icon, CharSequence title, PendingIntent intent) {
-        b.addAction(icon, title, intent);
-    }
-
-    public void addBigTextStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, CharSequence bigText) {
-        Notification.BigTextStyle style = new Notification.BigTextStyle(b)
+        Notification.BigTextStyle style = new Notification.BigTextStyle(b.getBuilder())
             .setBigContentTitle(bigContentTitle)
             .bigText(bigText);
         if (useSummary) {
             style.setSummaryText(summaryText);
-         }
+        }
     }
 
-    public void addBigPictureStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addBigPictureStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, Bitmap bigPicture, Bitmap bigLargeIcon,
             boolean bigLargeIconSet) {
-       Notification.BigPictureStyle style = new Notification.BigPictureStyle(b)
-           .setBigContentTitle(bigContentTitle)
-           .bigPicture(bigPicture);
-       if (bigLargeIconSet) {
-           style.bigLargeIcon(bigLargeIcon);
-       }
+        Notification.BigPictureStyle style = new Notification.BigPictureStyle(b.getBuilder())
+            .setBigContentTitle(bigContentTitle)
+            .bigPicture(bigPicture);
+        if (bigLargeIconSet) {
+            style.bigLargeIcon(bigLargeIcon);
+        }
         if (useSummary) {
             style.setSummaryText(summaryText);
-         }
+        }
     }
 
-    public void addInboxStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addInboxStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, ArrayList<CharSequence> texts) {
-        Notification.InboxStyle style = new Notification.InboxStyle(b)
+        Notification.InboxStyle style = new Notification.InboxStyle(b.getBuilder())
             .setBigContentTitle(bigContentTitle);
         if (useSummary) {
             style.setSummaryText(summaryText);
@@ -98,7 +149,43 @@
         }
     }
 
-    public Notification build() {
-        return b.build();
+    /**
+     * Get the extras Bundle from a notification using reflection. Extras were present in
+     * Jellybean notifications, but the field was private until KitKat.
+     */
+    public static Bundle getExtras(Notification notif) {
+        synchronized (sExtrasLock) {
+            if (sExtrasFieldAccessFailed) {
+                return null;
+            }
+            try {
+                if (sExtrasField == null) {
+                    Field extrasField = Notification.class.getDeclaredField("extras");
+                    if (!Bundle.class.isAssignableFrom(extrasField.getType())) {
+                        Log.e(TAG, "Notification.extras field is not of type Bundle");
+                        sExtrasFieldAccessFailed = true;
+                        return null;
+                    }
+                    extrasField.setAccessible(true);
+                    sExtrasField = extrasField;
+                }
+                Bundle extras = (Bundle) sExtrasField.get(notif);
+                if (extras == null) {
+                    extras = new Bundle();
+                    sExtrasField.set(notif, extras);
+                }
+                return extras;
+            } catch (IllegalAccessException e) {
+                Log.e(TAG, "Unable to access notification extras", e);
+            } catch (NoSuchFieldException e) {
+                Log.e(TAG, "Unable to access notification extras", e);
+            }
+            sExtrasFieldAccessFailed = true;
+            return null;
+        }
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return getExtras(notif).getBoolean(EXTRA_LOCAL_ONLY);
     }
 }
diff --git a/v4/jellybean/android/support/v4/view/ViewCompatJB.java b/v4/jellybean/android/support/v4/view/ViewCompatJB.java
index 7456d28..2db62fc 100644
--- a/v4/jellybean/android/support/v4/view/ViewCompatJB.java
+++ b/v4/jellybean/android/support/v4/view/ViewCompatJB.java
@@ -69,4 +69,12 @@
     public static ViewParent getParentForAccessibility(View view) {
         return view.getParentForAccessibility();
     }
+
+    public static int getMinimumWidth(View view) {
+        return view.getMinimumWidth();
+    }
+
+    public static int getMinimumHeight(View view) {
+        return view.getMinimumHeight();
+    }
 }
diff --git a/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
new file mode 100644
index 0000000..d6959e3
--- /dev/null
+++ b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
@@ -0,0 +1,102 @@
+/*
+ * 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.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+class NotificationCompatKitKat {
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+        private Bundle mExtras;
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int mProgressMax, int mProgress, boolean mProgressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                Bundle extras) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+            mExtras = extras;
+            if (localOnly) {
+                getExtras().putBoolean(NotificationCompatJellybean.EXTRA_LOCAL_ONLY, localOnly);
+            }
+        }
+
+        @Override
+        public void addAction(int icon, CharSequence title, PendingIntent intent) {
+            b.addAction(icon, title, intent);
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            if (mExtras != null) {
+                b.setExtras(mExtras);
+            }
+            return b.build();
+        }
+
+        private Bundle getExtras() {
+            if (mExtras == null) {
+                mExtras = new Bundle();
+            }
+            return mExtras;
+        }
+    }
+
+    public static Bundle getExtras(Notification notif) {
+        return notif.extras;
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return getExtras(notif).getBoolean(NotificationCompatJellybean.EXTRA_LOCAL_ONLY);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
new file mode 100644
index 0000000..dfa7e68
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.widget;
+
+import android.os.Build;
+
+public class DonutScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public DonutScrollerCompatTest() {
+        super(Build.VERSION_CODES.DONUT);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
new file mode 100644
index 0000000..a5c96b0
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.widget;
+
+import android.os.Build;
+
+public class GingerbreadScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public GingerbreadScrollerCompatTest() {
+        super(Build.VERSION_CODES.GINGERBREAD);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
new file mode 100644
index 0000000..ccdc68d
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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.widget;
+
+import android.os.Build;
+
+public class IcsScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public IcsScrollerCompatTest() {
+        super(Build.VERSION_CODES.ICE_CREAM_SANDWICH);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
new file mode 100644
index 0000000..93ab8bf
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -0,0 +1,62 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.view.animation.Interpolator;
+
+import java.lang.reflect.Constructor;
+
+abstract public class ScrollerCompatTestBase extends AndroidTestCase {
+
+    private final int mApiLevel;
+
+    private ScrollerCompat mScroller;
+
+    public ScrollerCompatTestBase(int apiLevel) {
+        mApiLevel = apiLevel;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Constructor<ScrollerCompat> constructor = ScrollerCompat.class
+                .getDeclaredConstructor(int.class, Context.class, Interpolator.class);
+        constructor.setAccessible(true);
+        mScroller = constructor.newInstance(mApiLevel, getContext(), null);
+    }
+
+    public void testTargetReached() throws InterruptedException {
+        mScroller.fling(0, 0, 0, 1000,
+                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        int target = mScroller.getFinalY();
+        while (mScroller.computeScrollOffset()) {
+            Thread.sleep(100);
+        }
+        assertEquals("given enough time, scroller should reach target position", target,
+                mScroller.getCurrY());
+    }
+
+    public void testAbort() throws InterruptedException {
+        mScroller.fling(0, 0, 0, 10000,
+                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        assertTrue("Scroller should have some offset", mScroller.computeScrollOffset());
+        mScroller.abortAnimation();
+        assertFalse("Scroller should clear offset after being aborted",
+                mScroller.computeScrollOffset());
+    }
+}
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index 82e4816..a9ab60d 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -20,7 +20,7 @@
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v7-appcompat
-LOCAL_SDK_VERSION := 19
+LOCAL_SDK_VERSION := current
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAVA_LIBRARIES += android-support-v4
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index d8aff0c..26a3660 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -7,7 +7,7 @@
 }
 
 android {
-    compileSdkVersion 19
+    compileSdkVersion 'current'
     buildToolsVersion "19.0.1"
 
     sourceSets {
diff --git a/v7/appcompat/project.properties b/v7/appcompat/project.properties
index dfa4dd0..91d2b02 100644
--- a/v7/appcompat/project.properties
+++ b/v7/appcompat/project.properties
@@ -11,5 +11,5 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-16
+target=android-19
 android.library=true
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index a8a6383..1ff246c 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -16,9 +16,17 @@
 
 package android.support.v7.app;
 
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
@@ -69,6 +77,11 @@
  */
 public abstract class ActionBar {
 
+    /** @hide */
+    @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface NavigationMode {}
+
     /**
      * Standard navigation mode. Consists of either a logo or icon and title text with an optional
      * subtitle. Clicking any of these elements will dispatch onOptionsItemSelected to the host
@@ -88,6 +101,17 @@
      */
     public static final int NAVIGATION_MODE_TABS = 2;
 
+    /** @hide */
+    @IntDef(flag=true, value={
+            DISPLAY_USE_LOGO,
+            DISPLAY_SHOW_HOME,
+            DISPLAY_HOME_AS_UP,
+            DISPLAY_SHOW_TITLE,
+            DISPLAY_SHOW_CUSTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface DisplayOptions {}
+
     /**
      * Use logo instead of icon if available. This flag will cause appropriate navigation modes to
      * use a wider logo in place of the standard icon.
@@ -185,7 +209,7 @@
      * @param resId Resource ID of a layout to inflate into the ActionBar.
      * @see #setDisplayOptions(int, int)
      */
-    public abstract void setCustomView(int resId);
+    public abstract void setCustomView(@LayoutRes int resId);
 
     /**
      * Set the icon to display in the 'home' section of the action bar. The action bar will use an
@@ -198,7 +222,7 @@
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
-    public abstract void setIcon(int resId);
+    public abstract void setIcon(@DrawableRes int resId);
 
     /**
      * Set the icon to display in the 'home' section of the action bar. The action bar will use an
@@ -224,7 +248,7 @@
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
-    public abstract void setLogo(int resId);
+    public abstract void setLogo(@DrawableRes int resId);
 
     /**
      * Set the logo to display in the 'home' section of the action bar. The action bar will use a
@@ -295,7 +319,7 @@
      * @see #setTitle(CharSequence)
      * @see #setDisplayOptions(int, int)
      */
-    public abstract void setTitle(int resId);
+    public abstract void setTitle(@StringRes int resId);
 
     /**
      * Set the action bar's subtitle. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
@@ -305,7 +329,7 @@
      * @see #setSubtitle(int)
      * @see #setDisplayOptions(int, int)
      */
-    public abstract void setSubtitle(CharSequence subtitle);
+    public abstract void setSubtitle(@Nullable CharSequence subtitle);
 
     /**
      * Set the action bar's subtitle. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
@@ -315,7 +339,7 @@
      * @see #setSubtitle(CharSequence)
      * @see #setDisplayOptions(int, int)
      */
-    public abstract void setSubtitle(int resId);
+    public abstract void setSubtitle(@StringRes int resId);
 
     /**
      * Set display options. This changes all display option bits at once. To change a limited subset
@@ -324,7 +348,7 @@
      * @param options A combination of the bits defined by the DISPLAY_ constants defined in
      *                ActionBar.
      */
-    public abstract void setDisplayOptions(int options);
+    public abstract void setDisplayOptions(@DisplayOptions int options);
 
     /**
      * Set selected display options. Only the options specified by mask will be changed. To change
@@ -339,7 +363,7 @@
      *                ActionBar.
      * @param mask    A bit mask declaring which display options should be changed.
      */
-    public abstract void setDisplayOptions(int options, int mask);
+    public abstract void setDisplayOptions(@DisplayOptions int options, int mask);
 
     /**
      * Set whether to display the activity logo rather than the activity icon. A logo is often a
@@ -442,6 +466,7 @@
      *
      * @return The current ActionBar title or null.
      */
+    @Nullable
     public abstract CharSequence getTitle();
 
     /**
@@ -450,6 +475,7 @@
      *
      * @return The current ActionBar subtitle or null.
      */
+    @Nullable
     public abstract CharSequence getSubtitle();
 
     /**
@@ -464,6 +490,7 @@
      *
      * @return The current navigation mode.
      */
+    @NavigationMode
     public abstract int getNavigationMode();
 
     /**
@@ -474,11 +501,12 @@
      * @see #NAVIGATION_MODE_LIST
      * @see #NAVIGATION_MODE_TABS
      */
-    public abstract void setNavigationMode(int mode);
+    public abstract void setNavigationMode(@NavigationMode int mode);
 
     /**
      * @return The current set of display options.
      */
+    @DisplayOptions
     public abstract int getDisplayOptions();
 
     /**
@@ -567,6 +595,7 @@
      *
      * @return The currently selected tab or null
      */
+    @Nullable
     public abstract Tab getSelectedTab();
 
     /**
@@ -679,7 +708,7 @@
      * @see #setDisplayHomeAsUpEnabled(boolean)
      * @see #setHomeActionContentDescription(int)
      */
-    public void setHomeAsUpIndicator(Drawable indicator) {}
+    public void setHomeAsUpIndicator(@Nullable Drawable indicator) {}
 
     /**
      * Set an alternate drawable to display next to the icon/logo/title
@@ -693,14 +722,14 @@
      * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
      * to provide a correct description of the action for accessibility support.</p>
      *
-     * @param resId Resource ID of a drawable to use for the up indicator, or null
+     * @param resId Resource ID of a drawable to use for the up indicator, or 0
      *              to use the theme's default
      *
      * @see #setDisplayOptions(int, int)
      * @see #setDisplayHomeAsUpEnabled(boolean)
      * @see #setHomeActionContentDescription(int)
      */
-    public void setHomeAsUpIndicator(int resId) {}
+    public void setHomeAsUpIndicator(@DrawableRes int resId) {}
 
     /**
      * Set an alternate description for the Home/Up action, when enabled.
@@ -719,7 +748,7 @@
      * @see #setHomeAsUpIndicator(int)
      * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
      */
-    public void setHomeActionContentDescription(CharSequence description) {}
+    public void setHomeActionContentDescription(@Nullable CharSequence description) {}
 
     /**
      * Set an alternate description for the Home/Up action, when enabled.
@@ -739,7 +768,7 @@
      * @see #setHomeAsUpIndicator(int)
      * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
      */
-    public void setHomeActionContentDescription(int resId) {}
+    public void setHomeActionContentDescription(@StringRes int resId) {}
 
     /**
      * Listener for receiving {@link ActionBar} navigation events.
@@ -846,7 +875,7 @@
          * @param resId Resource ID referring to the drawable to use as an icon
          * @return The current instance for call chaining
          */
-        public abstract Tab setIcon(int resId);
+        public abstract Tab setIcon(@DrawableRes int resId);
 
         /**
          * Set the text displayed on this tab. Text may be truncated if there is not room to display
@@ -864,7 +893,7 @@
          * @param resId A resource ID referring to the text that should be displayed
          * @return The current instance for call chaining
          */
-        public abstract Tab setText(int resId);
+        public abstract Tab setText(@StringRes int resId);
 
         /**
          * Set a custom view to be used for this tab. This overrides values set by {@link
@@ -882,7 +911,7 @@
          * @param layoutResId A layout resource to inflate and use as a custom tab view
          * @return The current instance for call chaining
          */
-        public abstract Tab setCustomView(int layoutResId);
+        public abstract Tab setCustomView(@LayoutRes int layoutResId);
 
         /**
          * Retrieve a previously set custom view for this tab.
@@ -927,7 +956,7 @@
          * @see #setContentDescription(CharSequence)
          * @see #getContentDescription()
          */
-        public abstract Tab setContentDescription(int resId);
+        public abstract Tab setContentDescription(@StringRes int resId);
 
         /**
          * Set a description of this tab's content for use in accessibility support. If no content
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
index b56b448..ae4c6c4 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.LayoutRes;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.FragmentActivity;
@@ -72,7 +73,7 @@
     }
 
     @Override
-    public void setContentView(int layoutResID) {
+    public void setContentView(@LayoutRes int layoutResID) {
         mImpl.setContentView(layoutResID);
     }
 
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
index 798e359..276aa19 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
@@ -44,7 +44,9 @@
     private static final String TAG = "ActionBarActivityDelegate";
 
     static ActionBarActivityDelegate createDelegate(ActionBarActivity activity) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+        if (Build.VERSION.SDK_INT >= 20) {
+            return new ActionBarActivityDelegateApi20(activity);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
             return new ActionBarActivityDelegateJBMR2(activity);
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
             return new ActionBarActivityDelegateJB(activity);
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateApi20.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateApi20.java
new file mode 100644
index 0000000..3a06fe4
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateApi20.java
@@ -0,0 +1,40 @@
+/*
+ * 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.app;
+
+import android.view.Window;
+
+class ActionBarActivityDelegateApi20 extends ActionBarActivityDelegateJBMR2 {
+
+    ActionBarActivityDelegateApi20(ActionBarActivity activity) {
+        super(activity);
+    }
+
+    @Override
+    Window.Callback createWindowCallbackWrapper(Window.Callback cb) {
+        return new WindowCallbackWrapperApi20(cb);
+    }
+
+    class WindowCallbackWrapperApi20 extends WindowCallbackWrapper {
+
+        WindowCallbackWrapperApi20(Window.Callback wrapped) {
+            super(wrapped);
+        }
+
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 2f561d1..ec7687a 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -17,6 +17,7 @@
 
 
 import android.content.Context;
+import android.support.annotation.MenuRes;
 import android.support.v7.internal.view.SupportMenuInflater;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPopupHelper;
@@ -96,7 +97,7 @@
      * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
      * @param menuRes Menu resource to inflate
      */
-    public void inflate(int menuRes) {
+    public void inflate(@MenuRes int menuRes) {
         getMenuInflater().inflate(menuRes, mMenu);
     }
 
diff --git a/v7/mediarouter/project.properties b/v7/mediarouter/project.properties
index dfa4dd0..484dab0 100644
--- a/v7/mediarouter/project.properties
+++ b/v7/mediarouter/project.properties
@@ -11,5 +11,5 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-16
+target=android-17
 android.library=true
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
index 2fd2488..3b14e2b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
@@ -17,6 +17,8 @@
 package android.support.v7.app;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ActionProvider;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouteSelector;
@@ -151,6 +153,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -161,7 +164,7 @@
      *
      * @param selector The selector, must not be null.
      */
-    public void setRouteSelector(MediaRouteSelector selector) {
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -195,6 +198,7 @@
      *
      * @return The dialog factory, never null.
      */
+    @NonNull
     public MediaRouteDialogFactory getDialogFactory() {
         return mDialogFactory;
     }
@@ -205,7 +209,7 @@
      *
      * @param factory The dialog factory, must not be null.
      */
-    public void setDialogFactory(MediaRouteDialogFactory factory) {
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
         if (factory == null) {
             throw new IllegalArgumentException("factory must not be null");
         }
@@ -222,6 +226,7 @@
     /**
      * Gets the associated media route button, or null if it has not yet been created.
      */
+    @Nullable
     public MediaRouteButton getMediaRouteButton() {
         return mButton;
     }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index c3d34ec..f5103fa 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.graphics.drawable.DrawableCompat;
@@ -144,6 +145,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -179,6 +181,7 @@
      *
      * @return The dialog factory, never null.
      */
+    @NonNull
     public MediaRouteDialogFactory getDialogFactory() {
         return mDialogFactory;
     }
@@ -189,7 +192,7 @@
      *
      * @param factory The dialog factory, must not be null.
      */
-    public void setDialogFactory(MediaRouteDialogFactory factory) {
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
         if (factory == null) {
             throw new IllegalArgumentException("factory must not be null");
         }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index bd28f51..3a87f02 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -19,6 +19,7 @@
 import android.app.Dialog;
 import android.content.Context;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.mediarouter.R;
@@ -73,6 +74,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -82,7 +84,7 @@
      *
      * @param selector The selector, must not be null.
      */
-    public void setRouteSelector(MediaRouteSelector selector) {
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -128,7 +130,7 @@
      * @param route The route to consider, never null.
      * @return True if the route should be included in the chooser dialog.
      */
-    public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+    public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
         return !route.isDefault() && route.isEnabled() && route.matchesSelector(mSelector);
     }
 
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
index 834b50d..1ae284f 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.app;
 
+import android.support.annotation.NonNull;
+
 /**
  * The media route dialog factory is responsible for creating the media route
  * chooser and controller dialogs as needed.
@@ -39,6 +41,7 @@
      *
      * @return The default media route dialog factory, never null.
      */
+    @NonNull
     public static MediaRouteDialogFactory getDefault() {
         return sDefault;
     }
@@ -51,6 +54,7 @@
      *
      * @return The media route chooser dialog fragment, must not be null.
      */
+    @NonNull
     public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
         return new MediaRouteChooserDialogFragment();
     }
@@ -63,6 +67,7 @@
      *
      * @return The media route controller dialog fragment, must not be null.
      */
+    @NonNull
     public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
         return new MediaRouteControllerDialogFragment();
     }
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index 54596b1..e011877 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v7.media.MediaRouter.ControlRequestCallback;
 
 /**
@@ -73,7 +75,7 @@
      *
      * @param context The context.
      */
-    public MediaRouteProvider(Context context) {
+    public MediaRouteProvider(@NonNull Context context) {
         this(context, null);
     }
 
@@ -116,7 +118,7 @@
      *
      * @param callback The callback to use, or null if none.
      */
-    public final void setCallback(Callback callback) {
+    public final void setCallback(@Nullable Callback callback) {
         MediaRouter.checkCallingThread();
         mCallback = callback;
     }
@@ -129,6 +131,7 @@
      *
      * @see #onDiscoveryRequestChanged
      */
+    @Nullable
     public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
         return mDiscoveryRequest;
     }
@@ -184,7 +187,7 @@
      *
      * @see MediaRouter#addCallback
      */
-    public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+    public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
     }
 
     /**
@@ -199,6 +202,7 @@
      *
      * @see Callback#onDescriptorChanged
      */
+    @Nullable
     public final MediaRouteProviderDescriptor getDescriptor() {
         return mDescriptor;
     }
@@ -214,7 +218,7 @@
      *
      * @see Callback#onDescriptorChanged
      */
-    public final void setDescriptor(MediaRouteProviderDescriptor descriptor) {
+    public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
         MediaRouter.checkCallingThread();
 
         if (mDescriptor != descriptor) {
@@ -245,6 +249,7 @@
      * @return The route controller.  Returns null if there is no such route or if the route
      * cannot be controlled using the route controller interface.
      */
+    @Nullable
     public RouteController onCreateRouteController(String routeId) {
         return null;
     }
@@ -354,7 +359,7 @@
          *
          * @see MediaControlIntent
          */
-        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+        public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
             return false;
         }
     }
@@ -369,8 +374,8 @@
          * @param provider The media route provider that changed, never null.
          * @param descriptor The new media route provider descriptor, or null if none.
          */
-        public void onDescriptorChanged(MediaRouteProvider provider,
-                MediaRouteProviderDescriptor descriptor) {
+        public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
+                @Nullable MediaRouteProviderDescriptor descriptor) {
         }
     }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
index c6869f3..4323d69 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
@@ -17,6 +17,8 @@
 
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -200,7 +202,7 @@
      * @param bundle The bundle, or null if none.
      * @return The new instance, or null if the bundle was null.
      */
-    public static MediaRouteSelector fromBundle(Bundle bundle) {
+    public static MediaRouteSelector fromBundle(@Nullable Bundle bundle) {
         return bundle != null ? new MediaRouteSelector(bundle, null) : null;
     }
 
@@ -220,7 +222,7 @@
          * Creates a media route selector descriptor builder whose initial contents are
          * copied from an existing selector.
          */
-        public Builder(MediaRouteSelector selector) {
+        public Builder(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -238,7 +240,8 @@
          * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
          * @return The builder instance for chaining.
          */
-        public Builder addControlCategory(String category) {
+        @NonNull
+        public Builder addControlCategory(@NonNull String category) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -259,7 +262,8 @@
          * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
          * @return The builder instance for chaining.
          */
-        public Builder addControlCategories(Collection<String> categories) {
+        @NonNull
+        public Builder addControlCategories(@NonNull Collection<String> categories) {
             if (categories == null) {
                 throw new IllegalArgumentException("categories must not be null");
             }
@@ -278,7 +282,8 @@
          * @param selector The media route selector whose contents are to be added.
          * @return The builder instance for chaining.
          */
-        public Builder addSelector(MediaRouteSelector selector) {
+        @NonNull
+        public Builder addSelector(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -290,6 +295,7 @@
         /**
          * Builds the {@link MediaRouteSelector media route selector}.
          */
+        @NonNull
         public MediaRouteSelector build() {
             if (mControlCategories == null) {
                 return EMPTY;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 7b83cc0..8d906b4 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -27,11 +27,16 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.hardware.display.DisplayManagerCompat;
 import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
 import android.util.Log;
 import android.view.Display;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -70,6 +75,17 @@
     final Context mContext;
     final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
 
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
+                    CALLBACK_FLAG_REQUEST_DISCOVERY,
+                    CALLBACK_FLAG_UNFILTERED_EVENTS
+            }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface CallbackFlags {}
+
     /**
      * Flag for {@link #addCallback}: Actively scan for routes while this callback
      * is registered.
@@ -156,7 +172,7 @@
      * @return The media router instance for the context.  The application must hold
      * a strong reference to this object as long as it is in use.
      */
-    public static MediaRouter getInstance(Context context) {
+    public static MediaRouter getInstance(@NonNull Context context) {
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
@@ -195,6 +211,7 @@
      *
      * @return The default route, which is guaranteed to never be null.
      */
+    @NonNull
     public RouteInfo getDefaultRoute() {
         checkCallingThread();
         return sGlobal.getDefaultRoute();
@@ -245,6 +262,7 @@
      * @see RouteInfo#supportsControlCategory
      * @see RouteInfo#supportsControlRequest
      */
+    @NonNull
     public RouteInfo getSelectedRoute() {
         checkCallingThread();
         return sGlobal.getSelectedRoute();
@@ -262,7 +280,8 @@
      * @see RouteInfo#matchesSelector
      * @see RouteInfo#isDefault
      */
-    public RouteInfo updateSelectedRoute(MediaRouteSelector selector) {
+    @NonNull
+    public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -284,7 +303,7 @@
      *
      * @param route The route to select.
      */
-    public void selectRoute(RouteInfo route) {
+    public void selectRoute(@NonNull RouteInfo route) {
         if (route == null) {
             throw new IllegalArgumentException("route must not be null");
         }
@@ -310,7 +329,7 @@
      * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
      * @return True if a matching route may be available.
      */
-    public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
+    public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -407,7 +426,8 @@
      * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
      * @see #removeCallback
      */
-    public void addCallback(MediaRouteSelector selector, Callback callback, int flags) {
+    public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
+            @CallbackFlags int flags) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -452,7 +472,7 @@
      * @param callback The callback to remove.
      * @see #addCallback
      */
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback must not be null");
         }
@@ -491,7 +511,7 @@
      * @see MediaRouteProvider
      * @see #removeCallback
      */
-    public void addProvider(MediaRouteProvider providerInstance) {
+    public void addProvider(@NonNull MediaRouteProvider providerInstance) {
         if (providerInstance == null) {
             throw new IllegalArgumentException("providerInstance must not be null");
         }
@@ -515,7 +535,7 @@
      * @see MediaRouteProvider
      * @see #addCallback
      */
-    public void removeProvider(MediaRouteProvider providerInstance) {
+    public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
         if (providerInstance == null) {
             throw new IllegalArgumentException("providerInstance must not be null");
         }
@@ -538,7 +558,7 @@
      *
      * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
      */
-    public void addRemoteControlClient(Object remoteControlClient) {
+    public void addRemoteControlClient(@NonNull Object remoteControlClient) {
         if (remoteControlClient == null) {
             throw new IllegalArgumentException("remoteControlClient must not be null");
         }
@@ -555,7 +575,7 @@
      *
      * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
      */
-    public void removeRemoteControlClient(Object remoteControlClient) {
+    public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
         if (remoteControlClient == null) {
             throw new IllegalArgumentException("remoteControlClient must not be null");
         }
@@ -608,6 +628,11 @@
         private Bundle mExtras;
         private MediaRouteDescriptor mDescriptor;
 
+        /** @hide */
+        @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackType {}
+
         /**
          * The default playback type, "local", indicating the presentation of the media
          * is happening on the same device (e.g. a phone, a tablet) as where it is
@@ -625,6 +650,11 @@
          */
         public static final int PLAYBACK_TYPE_REMOTE = 1;
 
+        /** @hide */
+        @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackVolume {}
+
         /**
          * Playback information indicating the playback volume is fixed, i.e. it cannot be
          * controlled from this object. An example of fixed playback volume is a remote player,
@@ -670,6 +700,7 @@
          *
          * @return The unique id of the route, never null.
          */
+        @NonNull
         public String getId() {
             return mUniqueId;
         }
@@ -697,6 +728,7 @@
          *
          * @return The description of the route, or null if none.
          */
+        @Nullable
         public String getDescription() {
             return mDescription;
         }
@@ -768,7 +800,7 @@
          * @return True if the route supports at least one of the capabilities
          * described in the media route selector.
          */
-        public boolean matchesSelector(MediaRouteSelector selector) {
+        public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -794,7 +826,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlCategory(String category) {
+        public boolean supportsControlCategory(@NonNull String category) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -829,7 +861,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlAction(String category, String action) {
+        public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -862,7 +894,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlRequest(Intent intent) {
+        public boolean supportsControlRequest(@NonNull Intent intent) {
             if (intent == null) {
                 throw new IllegalArgumentException("intent must not be null");
             }
@@ -895,7 +927,8 @@
          *
          * @see MediaControlIntent
          */
-        public void sendControlRequest(Intent intent, ControlRequestCallback callback) {
+        public void sendControlRequest(@NonNull Intent intent,
+                @Nullable ControlRequestCallback callback) {
             if (intent == null) {
                 throw new IllegalArgumentException("intent must not be null");
             }
@@ -910,6 +943,7 @@
          * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
          * or {@link #PLAYBACK_TYPE_REMOTE}.
          */
+        @PlaybackType
         public int getPlaybackType() {
             return mPlaybackType;
         }
@@ -929,6 +963,7 @@
          * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
          * or {@link #PLAYBACK_VOLUME_VARIABLE}.
          */
+        @PlaybackVolume
         public int getVolumeHandling() {
             return mVolumeHandling;
         }
@@ -1012,6 +1047,7 @@
          * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
          * @see android.app.Presentation
          */
+        @Nullable
         public Display getPresentationDisplay() {
             checkCallingThread();
             if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
@@ -1024,6 +1060,7 @@
          * Gets a collection of extra properties about this route that were supplied
          * by its media route provider, or null if none.
          */
+        @Nullable
         public Bundle getExtras() {
             return mExtras;
         }
diff --git a/v7/recyclerview/Android.mk b/v7/recyclerview/Android.mk
new file mode 100644
index 0000000..b3da0bd
--- /dev/null
+++ b/v7/recyclerview/Android.mk
@@ -0,0 +1,67 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# # Build the resources using the current SDK version.
+# # We do this here because the final static library must be compiled with an older
+# # SDK version than the resources.  The resources library and the R class that it
+# # contains will not be linked into the final static library.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-res
+# LOCAL_SDK_VERSION := current
+# LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+# LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+# LOCAL_AAPT_FLAGS := \
+# 	--auto-add-overlay
+# LOCAL_JAR_EXCLUDE_FILES := none
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean
+# LOCAL_SDK_VERSION := 16
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean)
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean MR1 APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean-mr1
+# LOCAL_SDK_VERSION := 17
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
+# LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-recyclerview-jellybean
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean MR2 APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean-mr2
+# LOCAL_SDK_VERSION := current
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
+# LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-recyclerview-jellybean-mr1
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-recyclerview
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+# LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-recyclerview-res
+LOCAL_JAVA_LIBRARIES := \
+        android-support-v4 \
+        android-support-annotations
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/recyclerview/AndroidManifest.xml b/v7/recyclerview/AndroidManifest.xml
new file mode 100644
index 0000000..421395f
--- /dev/null
+++ b/v7/recyclerview/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v7.recyclerview">
+    <uses-sdk android:minSdkVersion="7"/>
+</manifest>
diff --git a/v7/recyclerview/README.txt b/v7/recyclerview/README.txt
new file mode 100644
index 0000000..3c9de34
--- /dev/null
+++ b/v7/recyclerview/README.txt
@@ -0,0 +1 @@
+Library Project including RecyclerView and associated utilities.
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
new file mode 100644
index 0000000..f7d0607
--- /dev/null
+++ b/v7/recyclerview/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'recyclerview-v7'
+
+dependencies {
+    compile project(':support-v4')
+}
+
+android {
+    compileSdkVersion 7
+    buildToolsVersion "19.0.1"
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDir 'src'
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/src'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
new file mode 100644
index 0000000..bd4cae7
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -0,0 +1,1473 @@
+/*
+ * 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 languag`e governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.graphics.PointF;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
+ * similar functionality to {@link android.widget.ListView}.
+ */
+public class LinearLayoutManager extends RecyclerView.LayoutManager {
+
+    private static final String TAG = "LinearLayoutManager";
+
+    private static final boolean DEBUG = false;
+
+    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+
+    public static final int VERTICAL = LinearLayout.VERTICAL;
+
+    public static final int INVALID_OFFSET = Integer.MIN_VALUE;
+
+
+    /**
+     * While trying to find next view to focus, LinearLayoutManager will not try to scroll more
+     * than
+     * this factor times the total space of the list. If layout is vertical, total space is the
+     * height minus padding, if layout is horizontal, total space is the width minus padding.
+     */
+    private static final float MAX_SCROLL_FACTOR = 0.33f;
+
+
+    /**
+     * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    private int mOrientation;
+
+    /**
+     * Helper class that keeps temporary rendering state.
+     * It does not keep state after rendering is complete but we still keep a reference to re-use
+     * the same object.
+     */
+    private RenderState mRenderState;
+
+    /**
+     * Many calculations are made depending on orientation. To keep it clean, this interface
+     * helps {@link LinearLayoutManager} make those decisions.
+     * Based on {@link #mOrientation}, an implementation is lazily created in
+     * {@link #ensureRenderState} method.
+     */
+    private OrientationHelper mOrientationHelper;
+
+    /**
+     * We need to track this so that we can ignore current position when it changes.
+     */
+    private boolean mLastStackFromEnd;
+
+
+    /**
+     * Defines if layout should be calculated from end to start.
+     *
+     * @see #mShouldReverseLayout
+     */
+    private boolean mReverseLayout = false;
+
+    /**
+     * This keeps the final value for how LayoutManager shouls start laying out views.
+     * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
+     * {@link #layoutChildren(RecyclerView.Adapter, RecyclerView.Recycler, boolean)} is run.
+     */
+    private boolean mShouldReverseLayout = false;
+
+    /**
+     * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
+     * it supports both orientations.
+     * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+     */
+    private boolean mStackFromEnd = false;
+
+    /**
+     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
+     * layout which will check this variable and re-layout accordingly.
+     */
+    private int mPendingScrollPosition = RecyclerView.NO_POSITION;
+
+    /**
+     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
+     * called.
+     */
+    private int mPendingScrollPositionOffset = INVALID_OFFSET;
+
+    private SavedState mPendingSavedState = null;
+
+    /**
+     * Creates a vertical LinearLayoutManager
+     *
+     * @param context Current context, will be used to access resources.
+     */
+    public LinearLayoutManager(Context context) {
+        this(context, VERTICAL, false);
+    }
+
+    /**
+     * @param context       Current context, will be used to access resources.
+     * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link
+     *                      #VERTICAL}.
+     * @param reverseLayout When set to true, renders the layout from end to start.
+     */
+    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+        setOrientation(orientation);
+        setReverseLayout(reverseLayout);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        if (mPendingSavedState != null) {
+            return new SavedState(mPendingSavedState);
+        }
+        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() -
+                        mOrientationHelper.getDecoratedEnd(refChild);
+                state.mAnchorPosition = getPosition(refChild);
+            } else {
+                final View refChild = getChildClosestToStart();
+                state.mAnchorPosition = getPosition(refChild);
+                state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) -
+                        mOrientationHelper.getStartAfterPadding();
+            }
+        } else {
+            state.mAnchorPosition = 0;
+            state.mAnchorOffset = 0;
+        }
+        state.mStackFromEnd = mStackFromEnd;
+        state.mReverseLayout = mReverseLayout;
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state instanceof SavedState) {
+            mPendingSavedState = (SavedState) state;
+            requestLayout();
+            if (DEBUG) {
+                Log.d(TAG, "loaded saved state");
+            }
+        } else if (DEBUG) {
+            Log.d(TAG, "invalid saved state class");
+        }
+    }
+
+    /**
+     * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
+     */
+    @Override
+    public boolean canScrollHorizontally() {
+        return mOrientation == HORIZONTAL;
+    }
+
+    /**
+     * @return true if {@link #getOrientation()} is {@link #VERTICAL}
+     */
+    @Override
+    public boolean canScrollVertically() {
+        return mOrientation == VERTICAL;
+    }
+
+    /**
+     * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+     */
+    public void setStackFromEnd(boolean stackFromEnd) {
+        mStackFromEnd = stackFromEnd;
+        requestLayout();
+    }
+
+    public boolean getStackFromEnd() {
+        return mStackFromEnd;
+    }
+
+    /**
+     * Returns the current orientaion of the layout.
+     *
+     * @return Current orientation.
+     * @see #mOrientation
+     * @see #setOrientation(int)
+     */
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
+     * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager}
+     * will do its best to keep scroll position.
+     *
+     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            throw new IllegalArgumentException("invalid orientation.");
+        }
+        if (orientation == mOrientation) {
+            return;
+        }
+        mOrientation = orientation;
+        mOrientationHelper = null;
+        requestLayout();
+    }
+
+    /**
+     * Calculates the view layout order. (e.g. from end to start or start to end)
+     * RTL layout support is applied automatically. So if layout is RTL and
+     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+     */
+    private void resolveShouldLayoutReverse() {
+        // A == B is the same result, but we rather keep it readable
+        if (mOrientation == VERTICAL || !isLayoutRTL()) {
+            mShouldReverseLayout = mReverseLayout;
+        } else {
+            mShouldReverseLayout = !mReverseLayout;
+        }
+    }
+
+    /**
+     * Returns if views are laid out from the opposite direction of the layout.
+     *
+     * @return If layout is reversed or not.
+     * @see {@link #setReverseLayout(boolean)}
+     */
+    public boolean getReverseLayout() {
+        return mReverseLayout;
+    }
+
+    /**
+     * Used to reverse item traversal and layout order.
+     * This behaves similar to the layout change for RTL views. When set to true, first item is
+     * rendered at the end of the UI, second item is render before it etc.
+     *
+     * For horizontal layouts, it depends on the layout direction.
+     * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
+     * render from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will render
+     * from LTR.
+     *
+     * If you are looking for the exact same behavior of
+     * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
+     * {@link #setStackFromEnd(boolean)}
+     */
+    public void setReverseLayout(boolean reverseLayout) {
+        if (reverseLayout == mReverseLayout) {
+            return;
+        }
+        mReverseLayout = reverseLayout;
+        requestLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View findViewByPosition(int position) {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return null;
+        }
+        final int firstChild = getPosition(getChildAt(0));
+        final int viewPosition = position - firstChild;
+        if (viewPosition >= 0 && viewPosition < childCount) {
+            return getChildAt(viewPosition);
+        }
+        return null;
+    }
+
+    /**
+     * <p>Returns the amount of extra space that should be rendered by LinearLayoutManager.
+     * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of
+     * items while smooth scrolling and 0 otherwise. You can override this method to implement your
+     * custom layout pre-cache logic.</p>
+     * <p>Laying out invisible elements will eventually come with performance cost. On the other
+     * hand, in places like smooth scrolling to an unknown location, this extra content helps
+     * LayoutManager to calculate a much smoother scrolling; which improves user experience.</p>
+     * <p>You can also use this if you are trying to pre-render your upcoming views.</p>
+     *
+     * @return The extra space that should be laid out (in pixels).
+     */
+    protected int getExtraLayoutSpace(RecyclerView.State state) {
+        if (state.hasTargetScrollPosition()) {
+            return mOrientationHelper.getTotalSpace();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.Adapter adapter,
+            int position) {
+        LinearSmoothScroller linearSmoothScroller =
+                new LinearSmoothScroller(recyclerView.getContext()) {
+                    @Override
+                    public PointF computeScrollVectorForPosition(int targetPosition) {
+                        return LinearLayoutManager.this
+                                .computeScrollVectorForPosition(targetPosition);
+                    }
+                };
+        linearSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(linearSmoothScroller);
+    }
+
+    public PointF computeScrollVectorForPosition(int targetPosition) {
+        if (getChildCount() == 0) {
+            return null;
+        }
+        final int firstChildPos = getPosition(getChildAt(0));
+        final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
+        if (mOrientation == HORIZONTAL) {
+            return new PointF(direction, 0);
+        } else {
+            return new PointF(0, direction);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
+            boolean structureChanged, RecyclerView.State state) {
+        // layout algorithm:
+        // 1) by checking children and other variables, find an anchor coordinate and an anchor
+        //  item position.
+        // 2) fill towards start, stacking from bottom
+        // 3) fill towards end, stacking from top
+        // 4) scroll to fulfill requirements like stack from bottom.
+        // create render state
+
+        if (mPendingSavedState != null) {
+            setOrientation(mPendingSavedState.mOrientation);
+            setReverseLayout(mPendingSavedState.mReverseLayout);
+            setStackFromEnd(mPendingSavedState.mStackFromEnd);
+            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
+        }
+        ensureRenderState();
+        // resolve layout direction
+        resolveShouldLayoutReverse();
+
+        // validate scroll position if exists
+        if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
+            // validate it
+            if (mPendingScrollPosition < 0 || mPendingScrollPosition >= adapter.getItemCount()) {
+                mPendingScrollPosition = RecyclerView.NO_POSITION;
+                mPendingScrollPositionOffset = INVALID_OFFSET;
+                if (DEBUG) {
+                    Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
+                }
+            }
+        }
+        // this value might be updated if there is a target scroll position without an offset
+        boolean layoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+
+        final boolean stackFromEndChanged = mLastStackFromEnd != mStackFromEnd;
+
+        int anchorCoordinate, anchorItemPosition;
+        if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
+            // if child is visible, try to make it a reference child and ensure it is fully visible.
+            // if child is not visible, align it depending on its virtual position.
+            anchorItemPosition = mPendingScrollPosition;
+            if (mPendingSavedState != null) {
+                // Anchor offset depends on how that child was laid out. Here, we update it
+                // according to our current view bounds
+                layoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+                if (layoutFromEnd) {
+                    anchorCoordinate = mOrientationHelper.getEndAfterPadding() -
+                            mPendingSavedState.mAnchorOffset;
+                } else {
+                    anchorCoordinate = mOrientationHelper.getStartAfterPadding() +
+                            mPendingSavedState.mAnchorOffset;
+                }
+            } else if (mPendingScrollPositionOffset == INVALID_OFFSET) {
+                View child = findViewByPosition(mPendingScrollPosition);
+                if (child != null) {
+                    final int startGap = mOrientationHelper.getDecoratedStart(child)
+                            - mOrientationHelper.getStartAfterPadding();
+                    final int endGap = mOrientationHelper.getEndAfterPadding() -
+                            mOrientationHelper.getDecoratedEnd(child);
+                    final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+                    if (childSize > mOrientationHelper.getTotalSpace()) {
+                        // item does not fit. fix depending on layout direction
+                        anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding()
+                                : mOrientationHelper.getStartAfterPadding();
+                    } else if (startGap < 0) {
+                        anchorCoordinate = mOrientationHelper.getStartAfterPadding();
+                        layoutFromEnd = false;
+                    } else if (endGap < 0) {
+                        anchorCoordinate = mOrientationHelper.getEndAfterPadding();
+                        layoutFromEnd = true;
+                    } else {
+                        anchorCoordinate = layoutFromEnd
+                                ? mOrientationHelper.getDecoratedEnd(child)
+                                : mOrientationHelper.getDecoratedStart(child);
+                    }
+                } else { // item is not visible.
+                    if (getChildCount() > 0) {
+                        // get position of any child, does not matter
+                        int pos = getPosition(getChildAt(0));
+                        if (mPendingScrollPosition < pos == mShouldReverseLayout) {
+                            anchorCoordinate = mOrientationHelper.getEndAfterPadding();
+                            layoutFromEnd = true;
+                        } else {
+                            anchorCoordinate = mOrientationHelper.getStartAfterPadding();
+                            layoutFromEnd = false;
+                        }
+                    } else {
+                        anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding()
+                                : mOrientationHelper.getStartAfterPadding();
+                    }
+                }
+            } else {
+                // override layout from end values for consistency
+                if (mShouldReverseLayout) {
+                    anchorCoordinate = mOrientationHelper.getEndAfterPadding()
+                            - mPendingScrollPositionOffset;
+                    layoutFromEnd = true;
+                } else {
+                    anchorCoordinate = mOrientationHelper.getStartAfterPadding()
+                            + mPendingScrollPositionOffset;
+                    layoutFromEnd = false;
+                }
+            }
+        } else if (getChildCount() > 0 && !stackFromEndChanged) {
+            if (layoutFromEnd) {
+                View referenceChild = getChildClosestToEnd();
+                anchorCoordinate = mOrientationHelper.getDecoratedEnd(referenceChild);
+                anchorItemPosition = getPosition(referenceChild);
+            } else {
+                View referenceChild = getChildClosestToStart();
+                anchorCoordinate = mOrientationHelper.getDecoratedStart(referenceChild);
+                anchorItemPosition = getPosition(referenceChild);
+            }
+        } else {
+            anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding() :
+                    mOrientationHelper.getStartAfterPadding();
+            anchorItemPosition = mStackFromEnd ? adapter.getItemCount() - 1 : 0;
+        }
+
+        detachAndScrapAttachedViews(recycler);
+        final int extraForStart;
+        final int extraForEnd;
+        final int extra = getExtraLayoutSpace(state);
+        boolean before = state.getTargetScrollPosition() < anchorItemPosition;
+        if (before == mShouldReverseLayout) {
+            extraForEnd = extra;
+            extraForStart = 0;
+        } else {
+            extraForStart = extra;
+            extraForEnd = 0;
+        }
+        // first fill towards start
+        updateRenderStateToFillStart(anchorItemPosition, anchorCoordinate);
+        mRenderState.mExtra = extraForStart;
+        if (!layoutFromEnd) {
+            mRenderState.mCurrentPosition += mRenderState.mItemDirection;
+        }
+        fill(recycler, adapter, mRenderState, state, false);
+
+        // fill towards end
+        updateRenderStateToFillEnd(anchorItemPosition, anchorCoordinate);
+        mRenderState.mExtra = extraForEnd;
+        if (layoutFromEnd) {
+            mRenderState.mCurrentPosition += mRenderState.mItemDirection;
+        }
+        fill(recycler, adapter, mRenderState, state, false);
+
+        // changes may cause gaps on the UI, try to fix them.
+        if (getChildCount() > 0) {
+            // because layout from end may be changed by scroll to position
+            // we re-calculate it.
+            // find which side we should check for gaps.
+            if (mShouldReverseLayout ^ mStackFromEnd) {
+                fixLayoutEndGap(adapter, recycler, state, true);
+                fixLayoutStartGap(adapter, recycler, state, false);
+            } else {
+                fixLayoutStartGap(adapter, recycler, state, true);
+                fixLayoutEndGap(adapter, recycler, state, false);
+            }
+        }
+
+        removeAndRecycleScrap(recycler);
+        mPendingScrollPosition = RecyclerView.NO_POSITION;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+        mLastStackFromEnd = mStackFromEnd;
+        mPendingSavedState = null; // we don't need this anymore
+        if (DEBUG) {
+            validateChildOrder();
+        }
+    }
+
+    private void fixLayoutEndGap(RecyclerView.Adapter adapter,
+            RecyclerView.Recycler recycler, RecyclerView.State state,
+            boolean canOffsetChildren) {
+        View endChild = getChildClosestToEnd();
+        int gap = mOrientationHelper.getEndAfterPadding()
+                - mOrientationHelper.getDecoratedEnd(endChild);
+        if (gap > 0) {
+            scrollBy(-gap, adapter, recycler, state);
+        } else {
+            return; // nothing to fix
+        }
+        if (canOffsetChildren) {
+            // re-calculate gap, see if we could fix it
+            gap = mOrientationHelper.getEndAfterPadding()
+                    - mOrientationHelper.getDecoratedEnd(endChild);
+            if (gap > 0) {
+                mOrientationHelper.offsetChildren(gap);
+            }
+        }
+    }
+
+    private void fixLayoutStartGap(RecyclerView.Adapter adapter,
+            RecyclerView.Recycler recycler, RecyclerView.State state,
+            boolean canOffsetChildren) {
+        View startChild = getChildClosestToStart();
+        int gap = mOrientationHelper.getDecoratedStart(startChild) -
+                mOrientationHelper.getStartAfterPadding();
+        if (gap > 0) {
+            // check if we should fix this gap.
+            scrollBy(gap, adapter, recycler, state);
+        } else {
+            return; // nothing to fix
+        }
+        if (canOffsetChildren) {
+            // re-calculate gap, see if we could fix it
+            gap = mOrientationHelper.getDecoratedStart(startChild) -
+                    mOrientationHelper.getStartAfterPadding();
+            if (gap > 0) {
+                mOrientationHelper.offsetChildren(-gap);
+            }
+        }
+    }
+
+    private void updateRenderStateToFillEnd(int itemPosition, int offset) {
+        mRenderState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
+        mRenderState.mItemDirection = mShouldReverseLayout ? RenderState.ITEM_DIRECTION_HEAD :
+                RenderState.ITEM_DIRECTION_TAIL;
+        mRenderState.mCurrentPosition = itemPosition;
+        mRenderState.mLayoutDirection = RenderState.LAYOUT_END;
+        mRenderState.mOffset = offset;
+        mRenderState.mScrollingOffset = RenderState.SCOLLING_OFFSET_NaN;
+    }
+
+    private void updateRenderStateToFillStart(int itemPosition, int offset) {
+        mRenderState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
+        mRenderState.mCurrentPosition = itemPosition;
+        mRenderState.mItemDirection = mShouldReverseLayout ? RenderState.ITEM_DIRECTION_TAIL :
+                RenderState.ITEM_DIRECTION_HEAD;
+        mRenderState.mLayoutDirection = RenderState.LAYOUT_START;
+        mRenderState.mOffset = offset;
+        mRenderState.mScrollingOffset = RenderState.SCOLLING_OFFSET_NaN;
+
+    }
+
+
+    private boolean isLayoutRTL() {
+        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    private void ensureRenderState() {
+        if (mRenderState == null) {
+            mRenderState = new RenderState();
+        }
+        if (mOrientationHelper == null) {
+            mOrientationHelper = mOrientation == HORIZONTAL ? createHorizontalOrientationHelper()
+                    : createVerticalOrientationHelper();
+        }
+    }
+
+    /**
+     * <p>Scroll the RecyclerView to make the position visible.</p>
+     *
+     * <p>RecyclerView will scroll the minimum amount that is necessary to make the
+     * target position visible. If you are looking for a similar behavior to
+     * {@link android.widget.ListView#setSelection(int)} or
+     * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
+     * {@link #scrollToPositionWithOffset(int, int)}.</p>
+     *
+     * <p>Note that scroll position change will not be reflected until the next layout call.</p>
+     *
+     * @param position Scroll to this adapter position
+     * @see #scrollToPositionWithOffset(int, int)
+     */
+    @Override
+    public void scrollToPosition(int position) {
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+        requestLayout();
+    }
+
+    /**
+     * <p>Scroll to the specified adapter position with the given offset from layout start.</p>
+     *
+     * <p>Note that scroll position change will not be reflected until the next layout call.</p>
+     *
+     * <p>If you are just trying to make a position visible, use {@link
+     * #scrollToPosition(int)}.</p>
+     *
+     * @param position Index (starting at 0) of the reference item.
+     * @param offset   The distance (in pixels) between the start edge of the item view and
+     *                 start edge of the RecyclerView.
+     * @see #setReverseLayout(boolean)
+     * @see #scrollToPosition(int)
+     */
+    public void scrollToPositionWithOffset(int position, int offset) {
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = offset;
+        requestLayout();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int scrollHorizontallyBy(int dx, RecyclerView.Adapter adapter,
+            RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (mOrientation == VERTICAL) {
+            return 0;
+        }
+        return scrollBy(dx, adapter, recycler, state);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int scrollVerticallyBy(int dy, RecyclerView.Adapter adapter,
+            RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (mOrientation == HORIZONTAL) {
+            return 0;
+        }
+        return scrollBy(dy, adapter, recycler, state);
+    }
+
+    @Override
+    public int computeHorizontalScrollOffset(RecyclerView.Adapter adapter) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        final int topPosition = getPosition(getChildClosestToStart());
+        return mShouldReverseLayout ? adapter.getItemCount() - 1 - topPosition : topPosition;
+    }
+
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.Adapter adapter) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        final int topPosition = getPosition(getChildClosestToStart());
+        return mShouldReverseLayout ? adapter.getItemCount() - 1 - topPosition : topPosition;
+    }
+
+    @Override
+    public int computeHorizontalScrollExtent(RecyclerView.Adapter adapter) {
+        return getChildCount();
+    }
+
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.Adapter adapter) {
+        return getChildCount();
+    }
+
+    @Override
+    public int computeHorizontalScrollRange(RecyclerView.Adapter adapter) {
+        return adapter.getItemCount();
+    }
+
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.Adapter adapter) {
+        return adapter.getItemCount();
+    }
+
+    private void updateRenderState(int layoutDirection, int requiredSpace,
+            boolean canUseExistingSpace, RecyclerView.State state) {
+        mRenderState.mExtra = getExtraLayoutSpace(state);
+        mRenderState.mLayoutDirection = layoutDirection;
+        int fastScrollSpace;
+        if (layoutDirection == RenderState.LAYOUT_END) {
+            // get the first child in the direction we are going
+            final View child = getChildClosestToEnd();
+            // the direction in which we are traversing children
+            mRenderState.mItemDirection = mShouldReverseLayout ? RenderState.ITEM_DIRECTION_HEAD
+                    : RenderState.ITEM_DIRECTION_TAIL;
+            mRenderState.mCurrentPosition = getPosition(child) + mRenderState.mItemDirection;
+            mRenderState.mOffset = mOrientationHelper.getDecoratedEnd(child);
+            // calculate how much we can scroll without adding new children (independent of layout)
+            fastScrollSpace = mOrientationHelper.getDecoratedEnd(child)
+                    - mOrientationHelper.getEndAfterPadding();
+
+        } else {
+            final View child = getChildClosestToStart();
+            mRenderState.mItemDirection = mShouldReverseLayout ? RenderState.ITEM_DIRECTION_TAIL
+                    : RenderState.ITEM_DIRECTION_HEAD;
+            mRenderState.mCurrentPosition = getPosition(child) + mRenderState.mItemDirection;
+            mRenderState.mOffset = mOrientationHelper.getDecoratedStart(child);
+            fastScrollSpace = -mOrientationHelper.getDecoratedStart(child)
+                    + mOrientationHelper.getStartAfterPadding();
+        }
+        mRenderState.mAvailable = requiredSpace;
+        if (canUseExistingSpace) {
+            mRenderState.mAvailable -= fastScrollSpace;
+        }
+        mRenderState.mScrollingOffset = fastScrollSpace;
+    }
+
+    private int scrollBy(int dy, RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        if (getChildCount() == 0 || dy == 0) {
+            return 0;
+        }
+        ensureRenderState();
+        final int layoutDirection = dy > 0 ? RenderState.LAYOUT_END : RenderState.LAYOUT_START;
+        final int absDy = Math.abs(dy);
+        updateRenderState(layoutDirection, absDy, true, state);
+        final int freeScroll = mRenderState.mScrollingOffset;
+        final int consumed = freeScroll + fill(recycler, adapter, mRenderState, state, false);
+        if (consumed < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Don't have any more elements to scroll");
+            }
+            return 0;
+        }
+        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+        mOrientationHelper.offsetChildren(-scrolled);
+        if (DEBUG) {
+            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+        }
+        return scrolled;
+    }
+
+    /**
+     * Recycles children between given indices.
+     *
+     * @param startIndex inclusive
+     * @param endIndex   exclusive
+     */
+    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
+        if (startIndex == endIndex) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
+        }
+        if (endIndex > startIndex) {
+            for (int i = endIndex - 1; i >= startIndex; i--) {
+                removeAndRecycleViewAt(i, recycler);
+            }
+        } else {
+            for (int i = startIndex; i > endIndex; i--) {
+                removeAndRecycleViewAt(i, recycler);
+            }
+        }
+    }
+
+    /**
+     * Recycles views that went out of bounds after scrolling towards the end of the layout.
+     *
+     * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
+     * @param dt       This can be used to add additional padding to the visible area. This is used
+     *                 to
+     *                 detect children that will go out of bounds after scrolling, without actually
+     *                 moving them.
+     */
+    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
+        if (dt < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Called recycle from start with a negative value. This might happen"
+                        + " during layout changes but may be sign of a bug");
+            }
+            return;
+        }
+        final int limit = mOrientationHelper.getStartAfterPadding() + dt;
+        final int childCount = getChildCount();
+        if (mShouldReverseLayout) {
+            for (int i = childCount - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                    recycleChildren(recycler, childCount - 1, i);
+                    return;
+                }
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                    recycleChildren(recycler, 0, i);
+                    return;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Recycles views that went out of bounds after scrolling towards the start of the layout.
+     *
+     * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
+     * @param dt       This can be used to add additional padding to the visible area. This is used
+     *                 to detect children that will go out of bounds after scrolling, without
+     *                 actually moving them.
+     */
+    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
+        final int childCount = getChildCount();
+        if (dt < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Called recycle from end with a negative value. This might happen"
+                        + " during layout changes but may be sign of a bug");
+            }
+            return;
+        }
+        final int limit = mOrientationHelper.getEndAfterPadding() - dt;
+        if (mShouldReverseLayout) {
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                    recycleChildren(recycler, 0, i);
+                    return;
+                }
+            }
+        } else {
+            for (int i = childCount - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                    recycleChildren(recycler, childCount - 1, i);
+                    return;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Helper method to call appropriate recycle method depending on current render layout
+     * direction
+     *
+     * @param recycler    Current recycler that is attached to RecyclerView
+     * @param renderState Current render state. Right now, this object does not change but
+     *                    we may consider moving it out of this view so passing around as a
+     *                    parameter for now, rather than accessing {@link #mRenderState}
+     * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int)
+     * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int)
+     * @see android.support.v7.widget.LinearLayoutManager.RenderState#mLayoutDirection
+     */
+    private void recycleByRenderState(RecyclerView.Recycler recycler, RenderState renderState) {
+        if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
+            recycleViewsFromEnd(recycler, renderState.mScrollingOffset);
+        } else {
+            recycleViewsFromStart(recycler, renderState.mScrollingOffset);
+        }
+    }
+
+    /**
+     * The magic functions :). Fills the given layout, defined by the renderState. This is fairly
+     * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
+     * and with little change, can be made publicly available as a helper class.
+     *
+     * @param recycler        Current recycler that is attached to RecyclerView
+     * @param adapter         Current adapter that is attached to RecyclerView
+     * @param renderState     Configuration on how we should fill out the available space.
+     * @param state           Context passed by the RecyclerView to control scroll steps.
+     * @param stopOnFocusable If true, filling stops in the first focusable new child
+     * @return Number of pixels that it added. Useful for scoll functions.
+     */
+    private int fill(RecyclerView.Recycler recycler, RecyclerView.Adapter adapter,
+            RenderState renderState, RecyclerView.State state,
+            boolean stopOnFocusable) {
+        // max offset we should set is mFastScroll + available
+        final int start = renderState.mAvailable;
+        if (renderState.mScrollingOffset != RenderState.SCOLLING_OFFSET_NaN) {
+            // TODO ugly bug fix. should not happen
+            if (renderState.mAvailable < 0) {
+                renderState.mScrollingOffset += renderState.mAvailable;
+            }
+            recycleByRenderState(recycler, renderState);
+        }
+        int remainingSpace = renderState.mAvailable + renderState.mExtra;
+        while (remainingSpace > 0 && renderState.hasMore(adapter)) {
+            View view = renderState.next(recycler, adapter);
+            if (mShouldReverseLayout) {
+                if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
+                    addView(view);
+                } else {
+                    addView(view, 0);
+                }
+            } else {
+                if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
+                    addView(view, 0);
+                } else {
+                    addView(view);
+                }
+            }
+            measureChildWithMargins(view, 0, 0);
+            int consumed = mOrientationHelper.getDecoratedMeasurement(view);
+            int left, top, right, bottom;
+            if (mOrientation == VERTICAL) {
+                if (isLayoutRTL()) {
+                    right = getWidth() - getPaddingRight();
+                    left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+                } else {
+                    left = getPaddingLeft();
+                    right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+                }
+                if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
+                    bottom = renderState.mOffset;
+                    top = renderState.mOffset - consumed;
+                } else {
+                    top = renderState.mOffset;
+                    bottom = renderState.mOffset + consumed;
+                }
+            } else {
+                top = getPaddingTop();
+                bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
+
+                if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
+                    right = renderState.mOffset;
+                    left = renderState.mOffset - consumed;
+                } else {
+                    left = renderState.mOffset;
+                    right = renderState.mOffset + consumed;
+                }
+            }
+            // We calculate everything with View's bounding box (which includes decor and margins)
+            // To calculate correct layout position, we subtract margins.
+            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
+            layoutDecorated(view, left + params.leftMargin, top + params.topMargin
+                    , right - params.rightMargin, bottom - params.bottomMargin);
+            if (DEBUG) {
+                Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+                        + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+                        + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
+            }
+            renderState.mOffset += consumed * renderState.mLayoutDirection;
+            renderState.mAvailable -= consumed;
+            // we keep a separate remaining space because mAvailable is important for recycling
+            remainingSpace -= consumed;
+
+            if (renderState.mScrollingOffset != RenderState.SCOLLING_OFFSET_NaN) {
+                renderState.mScrollingOffset += consumed;
+                if (renderState.mAvailable < 0) {
+                    renderState.mScrollingOffset += renderState.mAvailable;
+                }
+                recycleByRenderState(recycler, renderState);
+            }
+            if (stopOnFocusable && view.isFocusable()) {
+                break;
+            }
+
+            if (state != null && state.getTargetScrollPosition() == getPosition(view)) {
+                break;
+            }
+        }
+        if (DEBUG) {
+            validateChildOrder();
+        }
+        return start - renderState.mAvailable;
+    }
+
+    /**
+     * Converts a focusDirection to orientation.
+     *
+     * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+     *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+     *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+     *                       or 0 for not applicable
+     * @return {@link RenderState#LAYOUT_START} or {@link RenderState#LAYOUT_END} if focus direction
+     * is applicable to current state, {@link RenderState#INVALID_LAYOUT} otherwise.
+     */
+    private int convertFocusDirectionToLayoutDirection(int focusDirection) {
+        switch (focusDirection) {
+            case View.FOCUS_BACKWARD:
+                return RenderState.LAYOUT_START;
+            case View.FOCUS_FORWARD:
+                return RenderState.LAYOUT_END;
+            case View.FOCUS_UP:
+                return mOrientation == VERTICAL ? RenderState.LAYOUT_START
+                        : RenderState.INVALID_LAYOUT;
+            case View.FOCUS_DOWN:
+                return mOrientation == VERTICAL ? RenderState.LAYOUT_END
+                        : RenderState.INVALID_LAYOUT;
+            case View.FOCUS_LEFT:
+                return mOrientation == HORIZONTAL ? RenderState.LAYOUT_START
+                        : RenderState.INVALID_LAYOUT;
+            case View.FOCUS_RIGHT:
+                return mOrientation == HORIZONTAL ? RenderState.LAYOUT_END
+                        : RenderState.INVALID_LAYOUT;
+            default:
+                if (DEBUG) {
+                    Log.d(TAG, "Unknown focus request:" + focusDirection);
+                }
+                return RenderState.INVALID_LAYOUT;
+        }
+
+    }
+
+    /**
+     * Convenience method to find the child closes to start. Caller should check it has enough
+     * children.
+     *
+     * @return The child closes to start of the layout from user's perspective.
+     */
+    private View getChildClosestToStart() {
+        return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
+    }
+
+    /**
+     * Convenience method to find the child closes to end. Caller should check it has enough
+     * children.
+     *
+     * @return The child closes to end of the layout from user's perspective.
+     */
+    private View getChildClosestToEnd() {
+        return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Adapter adapter,
+            RecyclerView.Recycler recycler, RecyclerView.State state) {
+        resolveShouldLayoutReverse();
+        if (getChildCount() == 0) {
+            return null;
+        }
+
+        final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
+        if (layoutDir == RenderState.INVALID_LAYOUT) {
+            return null;
+        }
+        final View referenceChild;
+        if (layoutDir == RenderState.LAYOUT_START) {
+            referenceChild = getChildClosestToStart();
+        } else {
+            referenceChild = getChildClosestToEnd();
+        }
+        ensureRenderState();
+        final int maxScroll = (int) (MAX_SCROLL_FACTOR * (mOrientationHelper.getEndAfterPadding() -
+                mOrientationHelper.getStartAfterPadding()));
+        updateRenderState(layoutDir, maxScroll, false, state);
+        mRenderState.mScrollingOffset = RenderState.SCOLLING_OFFSET_NaN;
+        fill(recycler, adapter, mRenderState, state, true);
+        final View nextFocus;
+        if (layoutDir == RenderState.LAYOUT_START) {
+            nextFocus = getChildClosestToStart();
+        } else {
+            nextFocus = getChildClosestToEnd();
+        }
+        if (nextFocus == referenceChild || !nextFocus.isFocusable()) {
+            return null;
+        }
+        return nextFocus;
+    }
+
+    /**
+     * Used for debugging.
+     * Logs the internal representation of children to default logger.
+     */
+    private void logChildren() {
+        Log.d(TAG, "internal representation of views on the screen");
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            Log.d(TAG, "item " + getPosition(child) + ", coord:"
+                    + mOrientationHelper.getDecoratedStart(child));
+        }
+        Log.d(TAG, "==============");
+    }
+
+    /**
+     * Used for debugging.
+     * Validates that child views are laid out in correct order. This is important because rest of
+     * the algorithm relies on this constraint.
+     *
+     * In default layout, child 0 should be closest to screen position 0 and last child should be
+     * closest to position WIDTH or HEIGHT.
+     * In reverse layout, last child should be closes to screen position 0 and first child should
+     * be closest to position WIDTH  or HEIGHT
+     */
+    private void validateChildOrder() {
+        Log.d(TAG, "validating child count " + getChildCount());
+        if (getChildCount() < 1) {
+            return;
+        }
+        int lastPos = getPosition(getChildAt(0));
+        int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
+        if (mShouldReverseLayout) {
+            for (int i = 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                int pos = getPosition(child);
+                int screenLoc = mOrientationHelper.getDecoratedStart(child);
+                if (pos < lastPos) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid position. loc invalid? " +
+                            (screenLoc < lastScreenLoc));
+                }
+                if (screenLoc > lastScreenLoc) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid location");
+                }
+            }
+        } else {
+            for (int i = 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                int pos = getPosition(child);
+                int screenLoc = mOrientationHelper.getDecoratedStart(child);
+                if (pos < lastPos) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid position. loc invalid? " +
+                            (screenLoc < lastScreenLoc));
+                }
+                if (screenLoc < lastScreenLoc) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid location");
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
+     * space.
+     */
+    private static class RenderState {
+
+        final static String TAG = "LinearLayoutManager#RenderState";
+
+        final static int LAYOUT_START = -1;
+
+        final static int LAYOUT_END = 1;
+
+        final static int INVALID_LAYOUT = Integer.MIN_VALUE;
+
+        final static int ITEM_DIRECTION_HEAD = -1;
+
+        final static int ITEM_DIRECTION_TAIL = 1;
+
+        final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
+
+        /**
+         * Pixel offset where rendering should start
+         */
+        int mOffset;
+
+        /**
+         * Number of pixels that we should fill, in the layout direction.
+         */
+        int mAvailable;
+
+        /**
+         * Current position on the adapter to get the next item.
+         */
+        int mCurrentPosition;
+
+        /**
+         * Defines the direction in which the data adapter is traversed.
+         * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
+         */
+        int mItemDirection;
+
+        /**
+         * Defines the direction in which the layout is filled.
+         * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
+         */
+        int mLayoutDirection;
+
+        /**
+         * Used when RenderState is constructed in a scrolling state.
+         * It should be set the amount of scrolling we can make without creating a new view.
+         * Settings this is required for efficient view recycling.
+         */
+        int mScrollingOffset;
+
+        /**
+         * Used if you want to pre-layout items that are not yet visible.
+         * The difference with {@link #mAvailable} is that, when recycling, distance rendered for
+         * {@link #mExtra} is not considered to avoid recycling visible children.
+         */
+        int mExtra = 0;
+
+        /**
+         * @return true if there are more items in the data adapter
+         */
+        boolean hasMore(RecyclerView.Adapter adapter) {
+            return mCurrentPosition >= 0 && mCurrentPosition < adapter.getItemCount();
+        }
+
+        /**
+         * Gets the view for the next element that we should render.
+         * Also updates current item index to the next item, based on {@link #mItemDirection}
+         *
+         * @return The next element that we should render.
+         */
+        View next(RecyclerView.Recycler recycler, RecyclerView.Adapter adapter) {
+            final View view = recycler.getViewForPosition(adapter, mCurrentPosition);
+            mCurrentPosition += mItemDirection;
+            return view;
+        }
+
+        void log() {
+            Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" +
+                    mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
+        }
+    }
+
+    private OrientationHelper createVerticalOrientationHelper() {
+        return new OrientationHelper() {
+            @Override
+            public int getEndAfterPadding() {
+                return getHeight() - getPaddingBottom();
+            }
+
+            @Override
+            public void offsetChildren(int amount) {
+                offsetChildrenVertical(amount);
+            }
+
+            @Override
+            public int getStartAfterPadding() {
+                return getPaddingTop();
+            }
+
+            @Override
+            public int getDecoratedMeasurement(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedMeasurementInOther(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedEnd(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedBottom(view) + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedStart(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedTop(view) - params.topMargin;
+            }
+
+            @Override
+            public int getTotalSpace() {
+                return getHeight() - getPaddingTop() - getPaddingBottom();
+            }
+        };
+    }
+
+    private OrientationHelper createHorizontalOrientationHelper() {
+        return new OrientationHelper() {
+            @Override
+            public int getEndAfterPadding() {
+                return getWidth() - getPaddingRight();
+            }
+
+            @Override
+            public void offsetChildren(int amount) {
+                offsetChildrenHorizontal(amount);
+            }
+
+            @Override
+            public int getStartAfterPadding() {
+                return getPaddingLeft();
+            }
+
+            @Override
+            public int getDecoratedMeasurement(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedMeasurementInOther(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedEnd(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedRight(view) + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedStart(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return getDecoratedLeft(view) - params.leftMargin;
+            }
+
+            @Override
+            public int getTotalSpace() {
+                return getWidth() - getPaddingLeft() - getPaddingRight();
+            }
+        };
+    }
+
+
+    /**
+     * Helper interface to offload orientation based decisions
+     */
+    private static interface OrientationHelper {
+
+        /**
+         * @param view The view element to check
+         * @return The first pixel of the element
+         * @see #getDecoratedEnd(android.view.View)
+         */
+        int getDecoratedStart(View view);
+
+        /**
+         * @param view The view element to check
+         * @return The last pixel of the element
+         * @see #getDecoratedStart(android.view.View)
+         */
+        int getDecoratedEnd(View view);
+
+        /**
+         * @param view The view element to check
+         * @return Total space occupied by this view
+         */
+        int getDecoratedMeasurement(View view);
+
+        /**
+         * @param view The view element to check
+         * @return Total space occupied by this view in the perpendicular orientation to current one
+         */
+        int getDecoratedMeasurementInOther(View view);
+
+        /**
+         * @return The very first pixel we can draw.
+         */
+        int getStartAfterPadding();
+
+        /**
+         * @return The last pixel we can draw
+         */
+        int getEndAfterPadding();
+
+        /**
+         * Offsets all children's positions by the given amount
+         *
+         * @param amount Value to add to each child's layout parameters
+         */
+        void offsetChildren(int amount);
+
+        /**
+         * Returns the total space to layout.
+         *
+         * @return Total space to layout children
+         */
+        int getTotalSpace();
+    }
+
+    static class SavedState implements Parcelable {
+
+        int mOrientation;
+
+        int mAnchorPosition;
+
+        int mAnchorOffset;
+
+        boolean mReverseLayout;
+
+        boolean mStackFromEnd;
+
+        boolean mAnchorLayoutFromEnd;
+
+
+        public SavedState() {
+
+        }
+
+        SavedState(Parcel in) {
+            mOrientation = in.readInt();
+            mAnchorPosition = in.readInt();
+            mAnchorOffset = in.readInt();
+            mReverseLayout = in.readInt() == 1;
+            mStackFromEnd = in.readInt() == 1;
+            mAnchorLayoutFromEnd = in.readInt() == 1;
+        }
+
+        public SavedState(SavedState other) {
+            mOrientation = other.mOrientation;
+            mAnchorPosition = other.mAnchorPosition;
+            mAnchorOffset = other.mAnchorOffset;
+            mReverseLayout = other.mReverseLayout;
+            mStackFromEnd = other.mStackFromEnd;
+            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mOrientation);
+            dest.writeInt(mAnchorPosition);
+            dest.writeInt(mAnchorOffset);
+            dest.writeInt(mReverseLayout ? 1 : 0);
+            dest.writeInt(mStackFromEnd ? 1 : 0);
+            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
new file mode 100644
index 0000000..b3f286b
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
@@ -0,0 +1,332 @@
+/*
+ * 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.PointF;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * {@link RecyclerView.SmoothScroller} implementation which uses
+ * {@link android.view.animation.LinearInterpolator} until the target position becames a child of
+ * the RecyclerView and then uses
+ * {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position.
+ */
+abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
+
+    private static final String TAG = "LinearSmoothScroller";
+
+    private static final boolean DEBUG = false;
+
+    private static final float MILLISECONDS_PER_INCH = 25f;
+
+    private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
+
+    /**
+     * Align child view's left or top with parent view's left or top
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_START = -1;
+
+    /**
+     * Align child view's right or bottom with parent view's right or bottom
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_END = 1;
+
+    /**
+     * <p>Decides if the child should be snapped from start or end, depending on where it
+     * currently is in relation to its parent.</p>
+     * <p>For instance, if the view is virtually on the left of RecyclerView, using
+     * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_ANY = 0;
+
+    // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
+    // view is not laid out until interim target position is reached, we can detect the case before
+    // scrolling slows down and reschedule another interim target scroll
+    private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
+
+    protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
+
+    protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+    protected PointF mTargetVector;
+
+    private final float MILLISECONDS_PER_PX;
+
+    // Temporary variables to keep track of the interim scroll target. These values do not
+    // point to a real item position, rather point to an estimated location pixels.
+    protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
+
+    public LinearSmoothScroller(Context context) {
+        MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onStart() {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
+        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);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
+        if (getChildCount() == 0) {
+            stop();
+        }
+        if (DEBUG && mTargetVector != null
+                && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
+            throw new IllegalStateException("Scroll happened in the opposite direction"
+                    + " of the target. Some calculations are wrong");
+        }
+        mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
+        mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
+
+        if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
+            updateActionForInterimTarget(action);
+        } // everything is valid, keep going
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onStop() {
+        mInterimTargetDx = mInterimTargetDy = 0;
+        mTargetVector = null;
+    }
+
+    /**
+     * Calculates the scroll speed.
+     *
+     * @param displayMetrics DisplayMetrics to be used for real dimension calculations
+     * @return The time (in ms) it should take for each pixel. For instance, if returned value is
+     * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
+     */
+    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+    }
+
+    /**
+     * <p>Calculates the time for deceleration so that transition from LinearInterpolator to
+     * DecelerateInterpolator looks smooth.</p>
+     *
+     * @param dx Distance to scroll
+     * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
+     * from LinearInterpolation
+     */
+    protected int calculateTimeForDeceleration(int dx) {
+        // we want to cover same area with the linear interpolator for the first 10% of the
+        // interpolation. After that, deceleration will take control.
+        // 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);
+    }
+
+    /**
+     * Calculates the time it should take to scroll the given distance (in pixels)
+     *
+     * @param dx Distance in pixels that we want to scroll
+     * @return Time in milliseconds
+     * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
+     */
+    protected int calculateTimeForScrolling(int dx) {
+        return Math.round(Math.abs(dx) * MILLISECONDS_PER_PX);
+    }
+
+    /**
+     * When scrolling towards a child view, this method defines whether we should align the left
+     * or the right edge of the child with the parent RecyclerView.
+     *
+     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+     * @see #SNAP_TO_START
+     * @see #SNAP_TO_END
+     * @see #SNAP_TO_ANY
+     */
+    protected int getHorizontalSnapPreference() {
+        return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
+                mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
+    }
+
+    /**
+     * When scrolling towards a child view, this method defines whether we should align the top
+     * or the bottom edge of the child with the parent RecyclerView.
+     *
+     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+     * @see #SNAP_TO_START
+     * @see #SNAP_TO_END
+     * @see #SNAP_TO_ANY
+     */
+    protected int getVerticalSnapPreference() {
+        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
+                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
+    }
+
+    /**
+     * When the target scroll position is not a child of the RecyclerView, this method calculates
+     * a direction vector towards that child and triggers a smooth scroll.
+     *
+     * @see #computeScrollVectorForPosition(int)
+     */
+    protected void updateActionForInterimTarget(Action action) {
+        // find an interim target position
+        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
+        if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
+            Log.e(TAG, "To support smooth scrolling, you should override \n"
+                    + "LayoutManager#computeScrollVectorForPosition.\n"
+                    + "Falling back to instant scroll");
+            final int target = getTargetPosition();
+            stop();
+            instantScrollToPosition(target);
+            return;
+        }
+        normalize(scrollVector);
+        mTargetVector = scrollVector;
+
+        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
+        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
+        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
+        // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
+        // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
+        // won't actually scroll more than what we need.
+        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
+                , (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
+                , (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
+    }
+
+    private int clampApplyScroll(int tmpDt, int dt) {
+        final int before = tmpDt;
+        tmpDt -= dt;
+        if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
+            return 0;
+        }
+        return tmpDt;
+    }
+
+    /**
+     * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
+     * {@link #calculateDyToMakeVisible(android.view.View, int)}
+     */
+    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
+            snapPreference) {
+        switch (snapPreference) {
+            case SNAP_TO_START:
+                return boxStart - viewStart;
+            case SNAP_TO_END:
+                return boxEnd - viewEnd;
+            case SNAP_TO_ANY:
+                final int dtStart = boxStart - viewStart;
+                if (dtStart > 0) {
+                    return dtStart;
+                }
+                final int dtEnd = boxEnd - viewEnd;
+                if (dtEnd < 0) {
+                    return dtEnd;
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("snap preference should be one of the"
+                        + " constants defined in SmoothScroller, starting with SNAP_");
+        }
+        return 0;
+    }
+
+    /**
+     * Calculates the vertical scroll amount necessary to make the given view fully visible
+     * inside the RecyclerView.
+     *
+     * @param view           The view which we want to make fully visible
+     * @param snapPreference The edge which the view should snap to when entering the visible
+     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+     *                       {@link #SNAP_TO_END}.
+     * @return The vertical scroll amount necessary to make the view visible with the given
+     * snap preference.
+     */
+    public int calculateDyToMakeVisible(View view, int snapPreference) {
+        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+        if (!layoutManager.canScrollVertically()) {
+            return 0;
+        }
+        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                view.getLayoutParams();
+        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
+        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
+        final int start = layoutManager.getPaddingTop();
+        final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
+        return calculateDtToFit(top, bottom, start, end, snapPreference);
+    }
+
+    /**
+     * Calculates the horizontal scroll amount necessary to make the given view fully visible
+     * inside the RecyclerView.
+     *
+     * @param view           The view which we want to make fully visible
+     * @param snapPreference The edge which the view should snap to when entering the visible
+     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+     *                       {@link #SNAP_TO_END}
+     * @return The vertical scroll amount necessary to make the view visible with the given
+     * snap preference.
+     */
+    public int calculateDxToMakeVisible(View view, int snapPreference) {
+        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+        if (!layoutManager.canScrollHorizontally()) {
+            return 0;
+        }
+        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                view.getLayoutParams();
+        final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
+        final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
+        final int start = layoutManager.getPaddingLeft();
+        final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
+        return calculateDtToFit(left, right, start, end, snapPreference);
+    }
+
+    abstract public PointF computeScrollVectorForPosition(int targetPosition);
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/PositionMap.java b/v7/recyclerview/src/android/support/v7/widget/PositionMap.java
new file mode 100644
index 0000000..3777937
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/PositionMap.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 java.util.ArrayList;
+
+/**
+ * Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
+ */
+class PositionMap<E> implements Cloneable {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    private int[] mKeys;
+    private Object[] mValues;
+    private int mSize;
+
+    /**
+     * Creates a new SparseArray containing no mappings.
+     */
+    public PositionMap() {
+        this(10);
+    }
+
+    /**
+     * Creates a new PositionMap containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public PositionMap(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_OBJECTS;
+        } else {
+            initialCapacity = idealIntArraySize(initialCapacity);
+            mKeys = new int[initialCapacity];
+            mValues = new Object[initialCapacity];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public PositionMap<E> clone() {
+        PositionMap<E> clone = null;
+        try {
+            clone = (PositionMap<E>) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(int key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    @SuppressWarnings("unchecked")
+    public E get(int key, E valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Alias for {@link #delete(int)}.
+     */
+    public void remove(int key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     */
+    public void removeAt(int index) {
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+
+    /**
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     */
+    public void removeAtRange(int index, int size) {
+        final int end = Math.min(mSize, index + size);
+        for (int i = index; i < end; i++) {
+            removeAt(i);
+        }
+    }
+
+    public void insertKeyRange(int keyStart, int count) {
+
+    }
+
+    public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
+
+    }
+
+    private void gc() {
+        // Log.e("SparseArray", "gc start with " + mSize);
+
+        int n = mSize;
+        int o = 0;
+        int[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                    values[i] = null;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, E value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
+            }
+
+            if (mSize >= mKeys.length) {
+                int n = idealIntArraySize(mSize + 1);
+
+                int[] nkeys = new int[n];
+                Object[] nvalues = new Object[n];
+
+                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    public int keyAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    @SuppressWarnings("unchecked")
+    public E valueAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    public void setValueAt(int index, E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that unlike most collections' {@code indexOf} methods,
+     * this method compares values using {@code ==} rather than {@code equals}.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = idealIntArraySize(pos + 1);
+
+            int[] nkeys = new int[n];
+            Object[] nvalues = new Object[n];
+
+            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(value);
+            } else {
+                buffer.append("(this Map)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    static int idealByteArraySize(int need) {
+        for (int i = 4; i < 32; i++)
+            if (need <= (1 << i) - 12)
+                return (1 << i) - 12;
+
+        return need;
+    }
+
+    static int idealBooleanArraySize(int need) {
+        return idealByteArraySize(need);
+    }
+
+    static int idealShortArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    static int idealCharArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    static int idealIntArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealFloatArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealObjectArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealLongArraySize(int need) {
+        return idealByteArraySize(need * 8) / 8;
+    }
+
+    static class ContainerHelpers {
+        static final boolean[] EMPTY_BOOLEANS = new boolean[0];
+        static final int[] EMPTY_INTS = new int[0];
+        static final long[] EMPTY_LONGS = new long[0];
+        static final Object[] EMPTY_OBJECTS = new Object[0];
+
+        // This is Arrays.binarySearch(), but doesn't do any argument validation.
+        static int binarySearch(int[] array, int size, int value) {
+            int lo = 0;
+            int hi = size - 1;
+
+            while (lo <= hi) {
+                final int mid = (lo + hi) >>> 1;
+                final int midVal = array[mid];
+
+                if (midVal < value) {
+                    lo = mid + 1;
+                } else if (midVal > value) {
+                    hi = mid - 1;
+                } else {
+                    return mid;  // value found
+                }
+            }
+            return ~lo;  // value not present
+        }
+    }
+
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
new file mode 100644
index 0000000..2a32b55
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -0,0 +1,5089 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.database.Observable;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.v4.util.Pools;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.EdgeEffectCompat;
+import android.support.v4.widget.ScrollerCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * A flexible view for providing a limited window into a large data set.
+ *
+ * <h3>Glossary of terms:</h3>
+ *
+ * <ul>
+ *     <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
+ *     that represent items in a data set.</li>
+ *     <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
+ *     <li><em>Index:</em> The index of an attached child view as used in a call to
+ *     {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
+ *     <li><em>Binding:</em> The process of preparing a child view to display data corresponding
+ *     to a <em>position</em> within the adapter.</li>
+ *     <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
+ *     position may be placed in a cache for later reuse to display the same type of data again
+ *     later. This can drastically improve performance by skipping initial layout inflation
+ *     or construction.</li>
+ *     <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
+ *     state during layout. Scrap views may be reused without becoming fully detached
+ *     from the parent RecyclerView, either unmodified if no rebinding is required or modified
+ *     by the adapter if the view was considered <em>dirty</em>.</li>
+ *     <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
+ *     being displayed.</li>
+ * </ul>
+ */
+public class RecyclerView extends ViewGroup {
+    private static final String TAG = "RecyclerView";
+
+    private static final boolean DEBUG = false;
+
+    private static final boolean DISPATCH_TEMP_DETACH = false;
+    public static final int HORIZONTAL = 0;
+    public static final int VERTICAL = 1;
+
+    public static final int NO_POSITION = -1;
+    public static final long NO_ID = -1;
+    public static final int INVALID_TYPE = -1;
+
+    private static final int MAX_SCROLL_DURATION = 2000;
+
+    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+    private final Recycler mRecycler = new Recycler();
+
+    private SavedState mPendingSavedState;
+
+    /**
+     * Note: this Runnable is only ever posted if:
+     * 1) We've been through first layout
+     * 2) We know we have a fixed size (mHasFixedSize)
+     * 3) We're attached
+     */
+    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
+        public void run() {
+            eatRequestLayout();
+            updateChildViews();
+            resumeRequestLayout(true);
+        }
+    };
+
+    private final Rect mTempRect = new Rect();
+
+    private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
+    private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
+
+    private Adapter mAdapter;
+    private LayoutManager mLayout;
+    private RecyclerListener mRecyclerListener;
+    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
+    private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
+            new ArrayList<OnItemTouchListener>();
+    private OnItemTouchListener mActiveOnItemTouchListener;
+    private boolean mIsAttached;
+    private boolean mHasFixedSize;
+    private boolean mFirstLayoutComplete;
+    private boolean mEatRequestLayout;
+    private boolean mLayoutRequestEaten;
+    private boolean mAdapterUpdateDuringMeasure;
+    private boolean mStructureChanged;
+    private final boolean mPostUpdatesOnAnimation;
+
+    private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
+
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * The RecyclerView is not currently scrolling.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_IDLE = 0;
+
+    /**
+     * The RecyclerView is currently being dragged by outside input such as user touch input.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_DRAGGING = 1;
+
+    /**
+     * The RecyclerView is currently animating to a final position while not under
+     * outside control.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_SETTLING = 2;
+
+    // Touch/scrolling handling
+
+    private int mScrollState = SCROLL_STATE_IDLE;
+    private int mScrollPointerId = INVALID_POINTER;
+    private VelocityTracker mVelocityTracker;
+    private int mInitialTouchX;
+    private int mInitialTouchY;
+    private int mLastTouchX;
+    private int mLastTouchY;
+    private final int mTouchSlop;
+    private final int mMinFlingVelocity;
+    private final int mMaxFlingVelocity;
+
+    private final ViewFlinger mViewFlinger = new ViewFlinger();
+
+    private final State mState = new State();
+
+    private OnScrollListener mScrollListener;
+
+    private static final Interpolator sQuinticInterpolator = new Interpolator() {
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t * t * t * t * t + 1.0f;
+        }
+    };
+
+    public RecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public RecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final int version = Build.VERSION.SDK_INT;
+        mPostUpdatesOnAnimation = version >= 16;
+
+        final ViewConfiguration vc = ViewConfiguration.get(context);
+        mTouchSlop = vc.getScaledTouchSlop();
+        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
+        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
+        setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
+    }
+
+    /**
+     * RecyclerView can perform several optimizations if it can know in advance that changes in
+     * adapter content cannot change the size of the RecyclerView itself.
+     * If your use of RecyclerView falls into this category, set this to true.
+     *
+     * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
+     */
+    public void setHasFixedSize(boolean hasFixedSize) {
+        mHasFixedSize = hasFixedSize;
+    }
+
+    /**
+     * @return true if the app has specified that changes in adapter content cannot change
+     * the size of the RecyclerView itself.
+     */
+    public boolean hasFixedSize() {
+        return mHasFixedSize;
+    }
+
+    /**
+     * Set a new adapter to provide child views on demand.
+     *
+     * @param adapter The new adapter to set, or null to set no adapter.
+     */
+    public void setAdapter(Adapter adapter) {
+        if (mAdapter != null) {
+            mAdapter.unregisterAdapterDataObserver(mObserver);
+        }
+        final Adapter oldAdapter = mAdapter;
+        mAdapter = adapter;
+        if (adapter != null) {
+            adapter.registerAdapterDataObserver(mObserver);
+        }
+        if (mLayout != null) {
+            mLayout.onAdapterChanged(oldAdapter, mAdapter);
+        }
+        mRecycler.onAdapterChanged(oldAdapter, mAdapter);
+        mStructureChanged = true;
+        markKnownViewsInvalid();
+        requestLayout();
+    }
+
+    /**
+     * Retrieves the previously set adapter or null if no adapter is set.
+     *
+     * @return The previously set adapter
+     * @see #setAdapter(Adapter)
+     */
+    public Adapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Register a listener that will be notified whenever a child view is recycled.
+     *
+     * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+     * that a child view is no longer needed. If an application associates expensive
+     * or heavyweight data with item views, this may be a good place to release
+     * or free those resources.</p>
+     *
+     * @param listener Listener to register, or null to clear
+     */
+    public void setRecyclerListener(RecyclerListener listener) {
+        mRecyclerListener = listener;
+    }
+
+    /**
+     * Set the {@link LayoutManager} that this RecyclerView will use.
+     *
+     * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
+     * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
+     * layout arrangements for child views. These arrangements are controlled by the
+     * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
+     *
+     * <p>Several default strategies are provided for common uses such as lists and grids.</p>
+     *
+     * @param layout LayoutManager to use
+     */
+    public void setLayoutManager(LayoutManager layout) {
+        if (layout == mLayout) {
+            return;
+        }
+
+        mRecycler.clear();
+        removeAllViews();
+        if (mLayout != null) {
+            if (mIsAttached) {
+                mLayout.onDetachedFromWindow(this);
+            }
+            mLayout.mRecyclerView = null;
+        }
+        mLayout = layout;
+        if (layout != null) {
+            if (layout.mRecyclerView != null) {
+                throw new IllegalArgumentException("LayoutManager " + layout +
+                        " is already attached to a RecyclerView: " + layout.mRecyclerView);
+            }
+            layout.mRecyclerView = this;
+            if (mIsAttached) {
+                mLayout.onAttachedToWindow(this);
+            }
+        }
+        requestLayout();
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState state = new SavedState(super.onSaveInstanceState());
+        if (mPendingSavedState != null) {
+            state.copyFrom(mPendingSavedState);
+        } else if (mLayout != null) {
+            state.mLayoutState = mLayout.onSaveInstanceState();
+        } else {
+            state.mLayoutState = null;
+        }
+
+        return state;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        mPendingSavedState = (SavedState) state;
+        super.onRestoreInstanceState(mPendingSavedState.getSuperState());
+        if (mLayout != null && mPendingSavedState.mLayoutState != null) {
+            mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
+        }
+    }
+
+    /**
+     * Return the {@link LayoutManager} currently responsible for
+     * layout policy for this RecyclerView.
+     *
+     * @return The currently bound LayoutManager
+     */
+    public LayoutManager getLayoutManager() {
+        return mLayout;
+    }
+
+    /**
+     * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
+     * if no pool is set for this view a new one will be created. See
+     * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
+     *
+     * @return The pool used to store recycled item views for reuse.
+     * @see #setRecycledViewPool(RecycledViewPool)
+     */
+    public RecycledViewPool getRecycledViewPool() {
+        return mRecycler.getRecycledViewPool();
+    }
+
+    /**
+     * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
+     * This can be useful if you have multiple RecyclerViews with adapters that use the same
+     * view types, for example if you have several data sets with the same kinds of item views
+     * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+     *
+     * @param pool Pool to set. If this parameter is null a new pool will be created and used.
+     */
+    public void setRecycledViewPool(RecycledViewPool pool) {
+        mRecycler.setRecycledViewPool(pool);
+    }
+
+    /**
+     * Set the number of offscreen views to retain before adding them to the potentially shared
+     * {@link #getRecycledViewPool() recycled view pool}.
+     *
+     * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
+     * a LayoutManager to reuse those views unmodified without needing to return to the adapter
+     * to rebind them.</p>
+     *
+     * @param size Number of views to cache offscreen before returning them to the general
+     *             recycled view pool
+     */
+    public void setItemViewCacheSize(int size) {
+        mRecycler.setViewCacheSize(size);
+    }
+
+    /**
+     * Return the current scrolling state of the RecyclerView.
+     *
+     * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
+     * {@link #SCROLL_STATE_SETTLING}
+     */
+    public int getScrollState() {
+        return mScrollState;
+    }
+
+    private void setScrollState(int state) {
+        if (state == mScrollState) {
+            return;
+        }
+        mScrollState = state;
+        if (state != SCROLL_STATE_SETTLING) {
+            stopScroll();
+        }
+        if (mScrollListener != null) {
+            mScrollListener.onScrollStateChanged(state);
+        }
+    }
+
+    /**
+     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+     * affect both measurement and drawing of individual item views.
+     *
+     * <p>Item decorations are ordered. Decorations placed earlier in the list will
+     * be run/queried/drawn first for their effects on item views. Padding added to views
+     * will be nested; a padding added by an earlier decoration will mean further
+     * item decorations in the list will be asked to draw/pad within the previous decoration's
+     * given area.</p>
+     *
+     * @param decor Decoration to add
+     * @param index Position in the decoration chain to insert this decoration at. If this value
+     *              is negative the decoration will be added at the end.
+     */
+    public void addItemDecoration(ItemDecoration decor, int index) {
+        if (mItemDecorations.isEmpty()) {
+            setWillNotDraw(false);
+        }
+        if (index < 0) {
+            mItemDecorations.add(decor);
+        } else {
+            mItemDecorations.add(index, decor);
+        }
+        markItemDecorInsetsDirty();
+        requestLayout();
+    }
+
+    /**
+     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+     * affect both measurement and drawing of individual item views.
+     *
+     * <p>Item decorations are ordered. Decorations placed earlier in the list will
+     * be run/queried/drawn first for their effects on item views. Padding added to views
+     * will be nested; a padding added by an earlier decoration will mean further
+     * item decorations in the list will be asked to draw/pad within the previous decoration's
+     * given area.</p>
+     *
+     * @param decor Decoration to add
+     */
+    public void addItemDecoration(ItemDecoration decor) {
+        addItemDecoration(decor, -1);
+    }
+
+    /**
+     * Remove an {@link ItemDecoration} from this RecyclerView.
+     *
+     * <p>The given decoration will no longer impact the measurement and drawing of
+     * item views.</p>
+     *
+     * @param decor Decoration to remove
+     * @see #addItemDecoration(ItemDecoration)
+     */
+    public void removeItemDecoration(ItemDecoration decor) {
+        mItemDecorations.remove(decor);
+        if (mItemDecorations.isEmpty()) {
+            setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
+        }
+        markItemDecorInsetsDirty();
+        requestLayout();
+    }
+
+    /**
+     * Set a listener that will be notified of any changes in scroll state or position.
+     *
+     * @param listener Listener to set or null to clear
+     */
+    public void setOnScrollListener(OnScrollListener listener) {
+        mScrollListener = listener;
+    }
+
+    /**
+     * Convenience method to scroll to a certain position.
+     *
+     * RecyclerView does not implement scrolling logic, rather forwards the call to
+     * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
+     * @param position Scroll to this adapter position
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
+     */
+    public void scrollToPosition(int position) {
+        stopScroll();
+        mLayout.scrollToPosition(position);
+        awakenScrollBars();
+    }
+
+    /**
+     * <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
+     * provide a custom smooth scroll logic, override
+     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.Adapter, int)} in your
+     * LayoutManager.</p>
+     *
+     * @param position The adapter position to scroll to
+     * @see LayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.Adapter, int)
+     */
+    public void smoothScrollToPosition(int position) {
+        mLayout.smoothScrollToPosition(this, mAdapter, position);
+    }
+
+    @Override
+    public void scrollTo(int x, int y) {
+        throw new UnsupportedOperationException(
+                "RecyclerView does not support scrolling to an absolute position.");
+    }
+
+    @Override
+    public void scrollBy(int x, int y) {
+        if (mLayout == null) {
+            throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+        }
+        final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+        final boolean canScrollVertical = mLayout.canScrollVertically();
+        if (canScrollHorizontal || canScrollVertical) {
+            scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0);
+        }
+    }
+
+    /**
+     * 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;
+        if (mAdapter != null) {
+            eatRequestLayout();
+            if (x != 0) {
+                final int hresult = mLayout.scrollHorizontallyBy(x, getAdapter(), mRecycler, mState);
+                overscrollX = x - hresult;
+            }
+            if (y != 0) {
+                final int vresult = mLayout.scrollVerticallyBy(y, getAdapter(), mRecycler, mState);
+                overscrollY = y - vresult;
+            }
+            resumeRequestLayout(false);
+        }
+
+        if (!mItemDecorations.isEmpty()) {
+            invalidate();
+        }
+        if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
+            pullGlows(overscrollX, overscrollY);
+        }
+        if (mScrollListener != null && (x != 0 || y != 0)) {
+            mScrollListener.onScrolled(x, y);
+        }
+        if (!awakenScrollBars()) {
+            invalidate();
+        }
+    }
+
+    /**
+     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
+     * range. This value is used to compute the length of the thumb within the scrollbar's track.
+     * </p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
+     *
+     * <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>
+     *
+     * @return The horizontal offset of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
+     * (RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeHorizontalScrollOffset() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mAdapter)
+                : 0;
+    }
+
+    /**
+     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
+     * horizontal range. This value is used to compute the length of the thumb within the
+     * scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <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>
+     *
+     * @return The horizontal extent of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollExtent(
+     * RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeHorizontalScrollExtent() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mAdapter)
+                : 0;
+    }
+
+    /**
+     * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <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>
+     *
+     * @return The total horizontal range represented by the vertical scrollbar
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollRange(
+     * RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeHorizontalScrollRange() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mAdapter) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
+     * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
+     *
+     * <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>
+     *
+     * @return The vertical offset of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
+     * (RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeVerticalScrollOffset() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mAdapter) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
+     * This value is used to compute the length of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <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>
+     *
+     * @return The vertical extent of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollExtent(
+     * RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeVerticalScrollExtent() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mAdapter) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <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>
+     *
+     * @return The total vertical range represented by the vertical scrollbar
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollRange(
+     * RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeVerticalScrollRange() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mAdapter) : 0;
+    }
+
+
+    void eatRequestLayout() {
+        if (!mEatRequestLayout) {
+            mEatRequestLayout = true;
+            mLayoutRequestEaten = false;
+        }
+    }
+
+    void resumeRequestLayout(boolean performLayoutChildren) {
+        if (mEatRequestLayout) {
+            if (performLayoutChildren && mLayoutRequestEaten &&
+                    mLayout != null && mAdapter != null) {
+                layoutChildren();
+            }
+            mEatRequestLayout = false;
+            mLayoutRequestEaten = false;
+        }
+    }
+
+    /**
+     * Animate a scroll by the given amount of pixels along either axis.
+     *
+     * @param dx Pixels to scroll horizontally
+     * @param dy Pixels to scroll vertically
+     */
+    public void smoothScrollBy(int dx, int dy) {
+        if (dx != 0 || dy != 0) {
+            mViewFlinger.smoothScrollBy(dx, dy);
+        }
+    }
+
+    /**
+     * Begin a standard fling with an initial velocity along each axis in pixels per second.
+     * If the velocity given is below the system-defined minimum this method will return false
+     * and no fling will occur.
+     *
+     * @param velocityX Initial horizontal velocity in pixels per second
+     * @param velocityY Initial vertical velocity in pixels per second
+     * @return true if the fling was started, false if the velocity was too low to fling
+     */
+    public boolean fling(int velocityX, int velocityY) {
+        if (Math.abs(velocityX) < mMinFlingVelocity) {
+            velocityX = 0;
+        }
+        if (Math.abs(velocityY) < mMinFlingVelocity) {
+            velocityY = 0;
+        }
+        velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
+        velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
+        if (velocityX != 0 || velocityY != 0) {
+            mViewFlinger.fling(velocityX, velocityY);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop any current scroll in progress, such as one started by
+     * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
+     */
+    public void stopScroll() {
+        mViewFlinger.stop();
+        mLayout.stopSmoothScroller();
+    }
+
+    /**
+     * Apply a pull to relevant overscroll glow effects
+     */
+    private void pullGlows(int overscrollX, int overscrollY) {
+        if (overscrollX < 0) {
+            if (mLeftGlow == null) {
+                mLeftGlow = new EdgeEffectCompat(getContext());
+                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+            }
+            mLeftGlow.onPull(-overscrollX / (float) getWidth());
+        } else if (overscrollX > 0) {
+            if (mRightGlow == null) {
+                mRightGlow = new EdgeEffectCompat(getContext());
+                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+            }
+            mRightGlow.onPull(overscrollX / (float) getWidth());
+        }
+
+        if (overscrollY < 0) {
+            if (mTopGlow == null) {
+                mTopGlow = new EdgeEffectCompat(getContext());
+                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+            }
+            mTopGlow.onPull(-overscrollY / (float) getHeight());
+        } else if (overscrollY > 0) {
+            if (mBottomGlow == null) {
+                mBottomGlow = new EdgeEffectCompat(getContext());
+                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+            }
+            mBottomGlow.onPull(overscrollY / (float) getHeight());
+        }
+
+        if (overscrollX != 0 || overscrollY != 0) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    private void releaseGlows() {
+        boolean needsInvalidate = false;
+        if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease();
+        if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease();
+        if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease();
+        if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease();
+        if (needsInvalidate) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    void absorbGlows(int velocityX, int velocityY) {
+        if (velocityX < 0) {
+            if (mLeftGlow == null) {
+                mLeftGlow = new EdgeEffectCompat(getContext());
+                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+            }
+            mLeftGlow.onAbsorb(-velocityX);
+        } else if (velocityX > 0) {
+            if (mRightGlow == null) {
+                mRightGlow = new EdgeEffectCompat(getContext());
+                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+            }
+            mRightGlow.onAbsorb(velocityX);
+        }
+
+        if (velocityY < 0) {
+            if (mTopGlow == null) {
+                mTopGlow = new EdgeEffectCompat(getContext());
+                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+            }
+            mTopGlow.onAbsorb(-velocityY);
+        } else if (velocityY > 0) {
+            if (mBottomGlow == null) {
+                mBottomGlow = new EdgeEffectCompat(getContext());
+                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+            }
+            mBottomGlow.onAbsorb(velocityY);
+        }
+
+        if (velocityX != 0 || velocityY != 0) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    // Focus handling
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        View result = mLayout.onInterceptFocusSearch(focused, direction);
+        if (result != null) {
+            return result;
+        }
+        final FocusFinder ff = FocusFinder.getInstance();
+        result = ff.findNextFocus(this, focused, direction);
+        if (result == null && mAdapter != null) {
+            eatRequestLayout();
+            result = mLayout.onFocusSearchFailed(focused, direction, mAdapter, mRecycler);
+            resumeRequestLayout(false);
+        }
+        return result != null ? result : super.focusSearch(focused, direction);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (!mLayout.onRequestChildFocus(this, child, focused)) {
+            mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+            offsetDescendantRectToMyCoords(focused, mTempRect);
+            offsetRectIntoDescendantCoords(child, mTempRect);
+            requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
+        }
+        super.requestChildFocus(child, focused);
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+        return mLayout.requestChildRectangleOnScreen(child, rect, immediate);
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if (!mLayout.onAddFocusables(this, views, direction, focusableMode)) {
+            super.addFocusables(views, direction, focusableMode);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mIsAttached = true;
+        mFirstLayoutComplete = false;
+        if (mLayout != null) {
+            mLayout.onAttachedToWindow(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mFirstLayoutComplete = false;
+
+        stopScroll();
+        // TODO Mark what our target position was if relevant, then we can jump there
+        // on reattach.
+        mIsAttached = false;
+        if (mLayout != null) {
+            mLayout.onDetachedFromWindow(this);
+        }
+    }
+
+    /**
+     * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
+     * to child views or this view's standard scrolling behavior.
+     *
+     * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
+     * returns true from
+     * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
+     * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
+     * for each incoming MotionEvent until the end of the gesture.</p>
+     *
+     * @param listener Listener to add
+     */
+    public void addOnItemTouchListener(OnItemTouchListener listener) {
+        mOnItemTouchListeners.add(listener);
+    }
+
+    /**
+     * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
+     *
+     * @param listener Listener to remove
+     */
+    public void removeOnItemTouchListener(OnItemTouchListener listener) {
+        mOnItemTouchListeners.remove(listener);
+        if (mActiveOnItemTouchListener == listener) {
+            mActiveOnItemTouchListener = null;
+        }
+    }
+
+    private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
+        final int action = e.getAction();
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
+            mActiveOnItemTouchListener = null;
+        }
+
+        final int listenerCount = mOnItemTouchListeners.size();
+        for (int i = 0; i < listenerCount; i++) {
+            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+            if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
+                mActiveOnItemTouchListener = listener;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean dispatchOnItemTouch(MotionEvent e) {
+        final int action = e.getAction();
+        if (mActiveOnItemTouchListener != null) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                // Stale state from a previous gesture, we're starting a new one. Clear it.
+                mActiveOnItemTouchListener = null;
+            } else {
+                mActiveOnItemTouchListener.onTouchEvent(this, e);
+                if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                    // Clean up for the next gesture.
+                    mActiveOnItemTouchListener = null;
+                }
+                return true;
+            }
+        }
+
+        // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
+        // as called from onInterceptTouchEvent; skip it.
+        if (action != MotionEvent.ACTION_DOWN) {
+            final int listenerCount = mOnItemTouchListeners.size();
+            for (int i = 0; i < listenerCount; i++) {
+                final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+                if (listener.onInterceptTouchEvent(this, e)) {
+                    mActiveOnItemTouchListener = listener;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        if (dispatchOnItemTouchIntercept(e)) {
+            cancelTouch();
+            return true;
+        }
+
+        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+        final boolean canScrollVertically = mLayout.canScrollVertically();
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(e);
+
+        final int action = MotionEventCompat.getActionMasked(e);
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
+                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+                if (mScrollState == SCROLL_STATE_SETTLING) {
+                    getParent().requestDisallowInterceptTouchEvent(true);
+                    setScrollState(SCROLL_STATE_DRAGGING);
+                }
+                break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN:
+                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
+                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
+                break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
+                if (index < 0) {
+                    Log.e(TAG, "Error processing scroll; pointer index for id " +
+                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+                    return false;
+                }
+
+                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
+                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
+                if (mScrollState != SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mInitialTouchX;
+                    final int dy = y - mInitialTouchY;
+                    boolean startScroll = false;
+                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (startScroll) {
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                        setScrollState(SCROLL_STATE_DRAGGING);
+                    }
+                }
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_UP: {
+                onPointerUp(e);
+            } break;
+
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.clear();
+            } break;
+
+            case MotionEvent.ACTION_CANCEL: {
+                cancelTouch();
+            }
+        }
+        return mScrollState == SCROLL_STATE_DRAGGING;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        if (dispatchOnItemTouch(e)) {
+            cancelTouch();
+            return true;
+        }
+
+        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+        final boolean canScrollVertically = mLayout.canScrollVertically();
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(e);
+
+        final int action = MotionEventCompat.getActionMasked(e);
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
+                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
+                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
+                if (index < 0) {
+                    Log.e(TAG, "Error processing scroll; pointer index for id " +
+                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+                    return false;
+                }
+
+                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
+                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
+                if (mScrollState != SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mInitialTouchX;
+                    final int dy = y - mInitialTouchY;
+                    boolean startScroll = false;
+                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (startScroll) {
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                        setScrollState(SCROLL_STATE_DRAGGING);
+                    }
+                }
+                if (mScrollState == SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mLastTouchX;
+                    final int dy = y - mLastTouchY;
+                    scrollByInternal(canScrollHorizontally ? -dx : 0,
+                            canScrollVertically ? -dy : 0);
+                }
+                mLastTouchX = x;
+                mLastTouchY = y;
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_UP: {
+                onPointerUp(e);
+            } break;
+
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
+                final float xvel = canScrollHorizontally ?
+                        -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
+                final float yvel = canScrollVertically ?
+                        -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
+                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
+                    setScrollState(SCROLL_STATE_IDLE);
+                }
+                mVelocityTracker.clear();
+                releaseGlows();
+            } break;
+
+            case MotionEvent.ACTION_CANCEL: {
+                cancelTouch();
+            } break;
+        }
+
+        return true;
+    }
+
+    private void cancelTouch() {
+        mVelocityTracker.clear();
+        releaseGlows();
+        setScrollState(SCROLL_STATE_IDLE);
+    }
+
+    private void onPointerUp(MotionEvent e) {
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+        if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) {
+            // Pick a new pointer to pick up the slack.
+            final int newIndex = actionIndex == 0 ? 1 : 0;
+            mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex);
+            mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f);
+            mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        if (mAdapterUpdateDuringMeasure) {
+            eatRequestLayout();
+            updateChildViews();
+            mAdapterUpdateDuringMeasure = false;
+            resumeRequestLayout(false);
+        }
+
+        mLayout.onMeasure(widthSpec, heightSpec);
+
+        final int widthSize = getMeasuredWidth();
+        final int heightSize = getMeasuredHeight();
+
+        if (mLeftGlow != null) mLeftGlow.setSize(heightSize, widthSize);
+        if (mTopGlow != null) mTopGlow.setSize(widthSize, heightSize);
+        if (mRightGlow != null) mRightGlow.setSize(heightSize, widthSize);
+        if (mBottomGlow != null) mBottomGlow.setSize(widthSize, heightSize);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mAdapter == null) {
+            Log.e(TAG, "No adapter attached; skipping layout");
+            return;
+        }
+        eatRequestLayout();
+        layoutChildren();
+        resumeRequestLayout(false);
+        mFirstLayoutComplete = true;
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mEatRequestLayout) {
+            super.requestLayout();
+        } else {
+            mLayoutRequestEaten = true;
+        }
+    }
+
+    void layoutChildren() {
+        mLayout.onLayoutChildren(mAdapter, mRecycler, mStructureChanged, mState);
+        // We don't need pending state anymore.
+        mPendingSavedState = null;
+        mStructureChanged = false;
+    }
+
+    void markItemDecorInsetsDirty() {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+        }
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+
+        final int count = mItemDecorations.size();
+        for (int i = 0; i < count; i++) {
+            mItemDecorations.get(i).onDrawOver(c, this);
+        }
+
+        boolean needsInvalidate = false;
+        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
+            final int restore = c.save();
+            c.rotate(270);
+            c.translate(-getHeight() + getPaddingTop(), 0);
+            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+        if (mTopGlow != null && !mTopGlow.isFinished()) {
+            c.translate(getPaddingLeft(), getPaddingTop());
+            needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
+        }
+        if (mRightGlow != null && !mRightGlow.isFinished()) {
+            final int restore = c.save();
+            final int width = getWidth();
+
+            c.rotate(90);
+            c.translate(-getPaddingTop(), -width);
+            needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
+            final int restore = c.save();
+            c.rotate(180);
+            c.translate(-getWidth() + getPaddingLeft(), -getHeight() + getPaddingTop());
+            needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+
+        if (needsInvalidate) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+
+        final int count = mItemDecorations.size();
+        for (int i = 0; i < count; i++) {
+            mItemDecorations.get(i).onDraw(c, this);
+        }
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateDefaultLayoutParams();
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateLayoutParams(p);
+    }
+
+    void updateChildViews() {
+        final int opCount = mPendingUpdates.size();
+        for (int i = 0; i < opCount; i++) {
+            final UpdateOp op = mPendingUpdates.get(i);
+            switch (op.cmd) {
+                case UpdateOp.ADD:
+                    if (DEBUG) {
+                        Log.d(TAG, "UpdateOp.ADD start=" + op.positionStart + " count=" +
+                                op.itemCount);
+                    }
+                    offsetPositionRecordsForInsert(op.positionStart, op.itemCount);
+
+                    mLayout.onItemsAdded(this, op.positionStart, op.itemCount);
+
+                    // TODO Animate it in
+                    break;
+                case UpdateOp.REMOVE:
+                    if (DEBUG) {
+                        Log.d(TAG, "UpdateOp.REMOVE start=" + op.positionStart + " count=" +
+                                op.itemCount);
+                    }
+                    offsetPositionRecordsForRemove(op.positionStart, op.itemCount);
+
+                    mLayout.onItemsRemoved(this, op.positionStart, op.itemCount);
+
+                    // TODO Animate it away
+                    break;
+                case UpdateOp.UPDATE:
+                    viewRangeUpdate(op.positionStart, op.itemCount);
+                    break;
+            }
+            recycleUpdateOp(op);
+        }
+        mPendingUpdates.clear();
+    }
+
+    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
+        boolean needsLayout = false;
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder != null && holder.mPosition >= positionStart) {
+                if (DEBUG) {
+                    Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " +
+                            holder + " now at position " + (holder.mPosition + itemCount));
+                }
+                holder.mPosition += itemCount;
+                needsLayout = true;
+                mStructureChanged = true;
+            }
+        }
+        mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
+        if (needsLayout) {
+            requestLayout();
+        }
+    }
+
+    void offsetPositionRecordsForRemove(int positionStart, int itemCount) {
+        boolean needsLayout = false;
+        final int positionEnd = positionStart + itemCount;
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder != null) {
+                if (holder.mPosition >= positionEnd) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
+                                " holder " + holder + " now at position " +
+                                (holder.mPosition - itemCount));
+                    }
+                    holder.mPosition -= itemCount;
+                    needsLayout = true;
+                    mStructureChanged = true;
+                } else if (holder.mPosition >= positionStart) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
+                                " holder " + holder + " now REMOVED");
+                    }
+                    holder.addFlags(ViewHolder.FLAG_REMOVED);
+                    needsLayout = true;
+                    mStructureChanged = true;
+                }
+            }
+        }
+        mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount);
+        if (needsLayout) {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Rebind existing views for the given range, or create as needed.
+     *
+     * @param positionStart Adapter position to start at
+     * @param itemCount Number of views that must explicitly be rebound
+     */
+    void viewRangeUpdate(int positionStart, int itemCount) {
+        final int childCount = getChildCount();
+        final int positionEnd = positionStart + itemCount;
+
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder == null) {
+                continue;
+            }
+
+            final int position = holder.getPosition();
+            if (position >= positionStart && position < positionEnd) {
+                holder.addFlags(ViewHolder.FLAG_UPDATE);
+                // Binding an attached view will request a layout if needed.
+                mAdapter.bindViewHolder(holder, holder.getPosition());
+            }
+        }
+        mRecycler.viewRangeUpdate(positionStart, itemCount);
+    }
+
+    /**
+     * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
+     * data change event.
+     */
+    void markKnownViewsInvalid() {
+        final int childCount = getChildCount();
+
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder != null) {
+                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+            }
+        }
+        mRecycler.markKnownViewsInvalid();
+    }
+
+    /**
+     * Schedule an update of data from the adapter to occur on the next frame.
+     * On newer platform versions this happens via the postOnAnimation mechanism and RecyclerView
+     * attempts to avoid relayouts if possible.
+     * On older platform versions the RecyclerView requests a layout the same way ListView does.
+     */
+    void postAdapterUpdate(UpdateOp op) {
+        mPendingUpdates.add(op);
+        if (mPendingUpdates.size() == 1) {
+            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
+                ViewCompat.postOnAnimation(this, mUpdateChildViewsRunnable);
+            } else {
+                mAdapterUpdateDuringMeasure = true;
+                requestLayout();
+            }
+        }
+    }
+
+    /**
+     * Retrieve the {@link ViewHolder} for the given child view.
+     *
+     * @param child Child of this RecyclerView to query for its ViewHolder
+     * @return The child view's ViewHolder
+     */
+    public ViewHolder getChildViewHolder(View child) {
+        if (child.getParent() != this) {
+            throw new IllegalArgumentException("View " + child + " is not a direct child of " +
+                    this);
+        }
+        return getChildViewHolderInt(child);
+    }
+
+    static ViewHolder getChildViewHolderInt(View child) {
+        if (child == null) {
+            return null;
+        }
+        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
+    }
+
+    /**
+     * Return the adapter position that the given child view corresponds to.
+     *
+     * @param child Child View to query
+     * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
+     */
+    public int getChildPosition(View child) {
+        final ViewHolder holder = getChildViewHolderInt(child);
+        return holder != null ? holder.getPosition() : NO_POSITION;
+    }
+
+    /**
+     * Return the stable item id that the given child view corresponds to.
+     *
+     * @param child Child View to query
+     * @return Item id corresponding to the given view or {@link #NO_ID}
+     */
+    public long getChildItemId(View child) {
+        if (mAdapter == null || !mAdapter.hasStableIds()) {
+            return NO_ID;
+        }
+        final ViewHolder holder = getChildViewHolderInt(child);
+        return holder != null ? holder.getItemId() : NO_ID;
+    }
+
+    /**
+     * Return the ViewHolder for the item in the given position of the data set.
+     *
+     * @param position The position of the item in the data set of the adapter
+     * @return The ViewHolder at <code>position</code>
+     */
+    public ViewHolder findViewHolderForPosition(int position) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder != null && holder.getPosition() == position) {
+                return holder;
+            }
+        }
+        return mRecycler.findViewHolderForPosition(position);
+    }
+
+    /**
+     * Return the ViewHolder for the item with the given id. The RecyclerView must
+     * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
+     * return a non-null value.
+     *
+     * @param id The id for the requested item
+     * @return The ViewHolder with the given <code>id</code>, of null if there
+     * is no such item.
+     */
+    public ViewHolder findViewHolderForItemId(long id) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+            if (holder != null && holder.getItemId() == id) {
+                return holder;
+            }
+        }
+        return mRecycler.findViewHolderForItemId(id);
+    }
+
+    /**
+     * 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)}
+     */
+    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
+     * @param y Vertical position in pixels to search
+     * @return The child view under (x, y) or null if no matching child is found
+     */
+    public View findChildViewUnder(float x, float y) {
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            final float translationX = ViewCompat.getTranslationX(child);
+            final float translationY = ViewCompat.getTranslationY(child);
+            if (x >= child.getLeft() + translationX &&
+                    x <= child.getRight() + translationX &&
+                    y >= child.getTop() + translationY &&
+                    y <= child.getBottom() + translationY) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 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)}
+     */
+    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}.
+     *
+     * @param dy Vertical pixel offset to apply to the bounds of all child views
+     */
+    public void offsetChildrenVertical(int dy) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            getChildAt(i).offsetTopAndBottom(dy);
+        }
+    }
+
+    /**
+     * Called when an item view is attached to this RecyclerView.
+     *
+     * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+     * of child views as they become attached. This will be called before a
+     * {@link LayoutManager} measures or lays out the view and is a good time to perform these
+     * changes.</p>
+     *
+     * @param child Child view that is now attached to this RecyclerView and its associated window
+     */
+    public void onChildAttachedToWindow(View child) {
+    }
+
+    /**
+     * Called when an item view is detached from this RecyclerView.
+     *
+     * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+     * of child views as they become detached. This will be called as a
+     * {@link LayoutManager} fully detaches the child view from the parent and its window.</p>
+     *
+     * @param child Child view that is now detached from this RecyclerView and its associated window
+     */
+    public void onChildDetachedFromWindow(View child) {
+    }
+
+    /**
+     * Offset the bounds of all child views by <code>dx</code> pixels.
+     * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+     *
+     * @param dx Horizontal pixel offset to apply to the bounds of all child views
+     */
+    public void offsetChildrenHorizontal(int dx) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            getChildAt(i).offsetLeftAndRight(dx);
+        }
+    }
+
+    Rect getItemDecorInsetsForChild(View child) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (!lp.mInsetsDirty) {
+            return lp.mDecorInsets;
+        }
+
+        final Rect insets = lp.mDecorInsets;
+        insets.set(0, 0, 0, 0);
+        final int decorCount = mItemDecorations.size();
+        for (int i = 0; i < decorCount; i++) {
+            mTempRect.set(0, 0, 0, 0);
+            mItemDecorations.get(i).getItemOffsets(mTempRect, lp.getViewPosition(), this);
+            insets.left += mTempRect.left;
+            insets.top += mTempRect.top;
+            insets.right += mTempRect.right;
+            insets.bottom += mTempRect.bottom;
+        }
+        lp.mInsetsDirty = false;
+        return insets;
+    }
+
+    private class ViewFlinger implements Runnable {
+        private int mLastFlingX;
+        private int mLastFlingY;
+        private ScrollerCompat mScroller;
+        private Interpolator mInterpolator = sQuinticInterpolator;
+
+
+        // When set to true, postOnAnimation callbacks are delayed until the run method completes
+        private boolean mEatRunOnAnimationRequest = false;
+
+        // Tracks if postAnimationCallback should be re-attached when it is done
+        private boolean mReSchedulePostAnimationCallback = false;
+
+        public ViewFlinger() {
+            mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator);
+        }
+
+        @Override
+        public void run() {
+            disableRunOnAnimationRequests();
+            // keep a local reference so that if it is changed during onAnimation method, it wont cause
+            // unexpected behaviors
+            final ScrollerCompat scroller = mScroller;
+            final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
+            if (scroller.computeScrollOffset()) {
+                final int x = scroller.getCurrX();
+                final int y = scroller.getCurrY();
+                final int dx = x - mLastFlingX;
+                final int dy = y - mLastFlingY;
+                mLastFlingX = x;
+                mLastFlingY = y;
+                int overscrollX = 0, overscrollY = 0;
+                if (mAdapter != null) {
+                    eatRequestLayout();
+                    if (dx != 0) {
+                        final int hresult = mLayout.scrollHorizontallyBy(dx, getAdapter(), mRecycler
+                                , mState);
+                        overscrollX = dx - hresult;
+                    }
+                    if (dy != 0) {
+                        final int vresult = mLayout.scrollVerticallyBy(dy, getAdapter(), mRecycler,
+                                mState);
+                        overscrollY = dy - vresult;
+                    }
+
+                    if (smoothScroller != null && !smoothScroller.isPendingInitialRun() &&
+                            smoothScroller.isRunning()) {
+                        smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+                    }
+                    resumeRequestLayout(false);
+                }
+                if (!mItemDecorations.isEmpty()) {
+                    invalidate();
+                }
+
+                if (overscrollX != 0 || overscrollY != 0) {
+                    final int vel = (int) scroller.getCurrVelocity();
+
+                    int velX = 0;
+                    if (overscrollX != x) {
+                        velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
+                    }
+
+                    int velY = 0;
+                    if (overscrollY != y) {
+                        velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
+                    }
+
+                    if (ViewCompat.getOverScrollMode(RecyclerView.this) !=
+                            ViewCompat.OVER_SCROLL_NEVER) {
+                        absorbGlows(velX, velY);
+                    }
+                    if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) &&
+                            (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
+                        scroller.abortAnimation();
+                    }
+                }
+
+                if (mScrollListener != null && (x != 0 || y != 0)) {
+                    mScrollListener.onScrolled(dx, dy);
+                }
+
+                if (!awakenScrollBars()) {
+                    invalidate();
+                }
+
+                if (scroller.isFinished()) {
+                    setScrollState(SCROLL_STATE_IDLE);
+                } else {
+                    postOnAnimation();
+                }
+            }
+            // call this after the onAnimation is complete not to have inconsistent callbacks etc.
+            if (smoothScroller != null && smoothScroller.isPendingInitialRun()) {
+                smoothScroller.onAnimation(0, 0);
+            }
+            enableRunOnAnimationRequests();
+        }
+
+        private void disableRunOnAnimationRequests() {
+            mReSchedulePostAnimationCallback = false;
+            mEatRunOnAnimationRequest = true;
+        }
+
+        private void enableRunOnAnimationRequests() {
+            mEatRunOnAnimationRequest = false;
+            if (mReSchedulePostAnimationCallback) {
+                postOnAnimation();
+            }
+        }
+
+        void postOnAnimation() {
+            if (mEatRunOnAnimationRequest) {
+                mReSchedulePostAnimationCallback = true;
+            } else {
+                ViewCompat.postOnAnimation(RecyclerView.this, this);
+            }
+        }
+
+        public void fling(int velocityX, int velocityY) {
+            setScrollState(SCROLL_STATE_SETTLING);
+            mLastFlingX = mLastFlingY = 0;
+            mScroller.fling(0, 0, velocityX, velocityY,
+                    Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+            postOnAnimation();
+        }
+
+        public void smoothScrollBy(int dx, int dy) {
+            smoothScrollBy(dx, dy, 0, 0);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int vx, int vy) {
+            smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
+        }
+
+        private float distanceInfluenceForSnapDuration(float f) {
+            f -= 0.5f; // center the values about 0.
+            f *= 0.3f * Math.PI / 2.0f;
+            return (float) Math.sin(f);
+        }
+
+        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
+            final int absDx = Math.abs(dx);
+            final int absDy = Math.abs(dy);
+            final boolean horizontal = absDx > absDy;
+            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
+            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
+            final int containerSize = horizontal ? getWidth() : getHeight();
+            final int halfContainerSize = containerSize / 2;
+            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
+            final float distance = halfContainerSize + halfContainerSize *
+                    distanceInfluenceForSnapDuration(distanceRatio);
+
+            final int duration;
+            if (velocity > 0) {
+                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+            } else {
+                float absDelta = (float) (horizontal ? absDx : absDy);
+                duration = (int) (((absDelta / containerSize) + 1) * 300);
+            }
+            return Math.min(duration, MAX_SCROLL_DURATION);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int duration) {
+            smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
+            if (mInterpolator != interpolator) {
+                mInterpolator = interpolator;
+                mScroller = ScrollerCompat.create(getContext(), interpolator);
+            }
+            setScrollState(SCROLL_STATE_SETTLING);
+            mLastFlingX = mLastFlingY = 0;
+            mScroller.startScroll(0, 0, dx, dy, duration);
+            postOnAnimation();
+        }
+
+        public void stop() {
+            removeCallbacks(this);
+            mScroller.abortAnimation();
+        }
+
+    }
+
+    private class RecyclerViewDataObserver extends AdapterDataObserver {
+        @Override
+        public void onChanged() {
+            if (mAdapter.hasStableIds()) {
+                // TODO Determine what actually changed
+                markKnownViewsInvalid();
+                mStructureChanged = true;
+                requestLayout();
+            } else {
+                markKnownViewsInvalid();
+                mStructureChanged = true;
+                requestLayout();
+            }
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            postAdapterUpdate(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            postAdapterUpdate(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            postAdapterUpdate(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
+        }
+    }
+
+    public static class RecycledViewPool {
+        private SparseArray<ArrayList<ViewHolder>> mScrap =
+                new SparseArray<ArrayList<ViewHolder>>();
+        private SparseIntArray mMaxScrap = new SparseIntArray();
+        private int mAttachCount = 0;
+
+        private static final int DEFAULT_MAX_SCRAP = 5;
+
+        public void clear() {
+            mScrap.clear();
+        }
+
+        /** @deprecated No longer needed */
+        public void reset(int typeCount) {
+        }
+
+        public void setMaxRecycledViews(int viewType, int max) {
+            mMaxScrap.put(viewType, max);
+            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
+            if (scrapHeap != null) {
+                while (scrapHeap.size() > max) {
+                    scrapHeap.remove(scrapHeap.size() - 1);
+                }
+            }
+        }
+
+        public ViewHolder getRecycledView(int viewType) {
+            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
+            if (scrapHeap != null && !scrapHeap.isEmpty()) {
+                final int index = scrapHeap.size() - 1;
+                final ViewHolder scrap = scrapHeap.get(index);
+                scrapHeap.remove(index);
+                return scrap;
+            }
+            return null;
+        }
+
+        public void putRecycledView(ViewHolder scrap) {
+            final int viewType = scrap.getItemViewType();
+            final ArrayList scrapHeap = getScrapHeapForType(viewType);
+            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
+                return;
+            }
+
+            scrap.mPosition = NO_POSITION;
+            scrap.mItemId = NO_ID;
+            scrap.clearFlagsForSharedPool();
+            scrapHeap.add(scrap);
+        }
+
+        void attach(Adapter adapter) {
+            mAttachCount++;
+        }
+
+        void detach() {
+            mAttachCount--;
+        }
+
+
+        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+            if (mAttachCount == 1) {
+                clear();
+            }
+        }
+
+        private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
+            ArrayList<ViewHolder> scrap = mScrap.get(viewType);
+            if (scrap == null) {
+                scrap = new ArrayList<ViewHolder>();
+                mScrap.put(viewType, scrap);
+                if (mMaxScrap.indexOfKey(viewType) < 0) {
+                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
+                }
+            }
+            return scrap;
+        }
+    }
+
+    /**
+     * A Recycler is responsible for managing scrapped or detached item views for reuse.
+     *
+     * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
+     * that has been marked for removal or reuse.</p>
+     *
+     * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
+     * an adapter's data set representing the data at a given position or item ID.
+     * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
+     * If not, the view can be quickly reused by the LayoutManager with no further work.
+     * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
+     * may be repositioned by a LayoutManager without remeasurement.</p>
+     */
+    public final class Recycler {
+        private final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
+
+        private final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
+        private int mViewCacheMax = DEFAULT_CACHE_SIZE;
+
+        private RecycledViewPool mRecyclerPool;
+
+        private static final int DEFAULT_CACHE_SIZE = 2;
+
+        /**
+         * Clear scrap views out of this recycler. Detached views contained within a
+         * recycled view pool will remain.
+         */
+        public void clear() {
+            mAttachedScrap.clear();
+            mCachedViews.clear();
+        }
+
+        /**
+         * Set the maximum number of detached, valid views we should retain for later use.
+         *
+         * @param viewCount Number of views to keep before sending views to the shared pool
+         */
+        public void setViewCacheSize(int viewCount) {
+            mViewCacheMax = viewCount;
+            while (mCachedViews.size() > viewCount) {
+                mCachedViews.remove(mCachedViews.size() - 1);
+            }
+        }
+
+        /**
+         * @deprecated Use {@link #getViewForPosition(Adapter, int)}
+         *             instead. This method will be removed.
+         */
+        public View getViewForPosition(int position) {
+            return getViewForPosition(mAdapter, position);
+        }
+
+        /**
+         * 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>
+         *
+         * <p>The Recycler may reuse a scrap or detached view from a shared pool if one is
+         * available for the correct view type. If the adapter has not indicated that the
+         * data at the given position has changed, the Recycler will attempt to hand back
+         * a scrap view that was previously initialized for that data without rebinding.</p>
+         *
+         * @param adapter Adapter to use for binding and creating views
+         * @param position Position to obtain a view for
+         * @return A view representing the data at <code>position</code> from <code>adapter</code>
+         */
+        public View getViewForPosition(Adapter adapter, int position) {
+            ViewHolder holder;
+            final int type = adapter.getItemViewType(position);
+            if (adapter.hasStableIds()) {
+                final long id = adapter.getItemId(position);
+                holder = getScrapViewForId(id, type);
+            } else {
+                holder = getScrapViewForPosition(position, type);
+            }
+
+            if (holder == null) {
+                holder = adapter.createViewHolder(RecyclerView.this, type);
+                if (DEBUG) Log.d(TAG, "getViewForPosition created new ViewHolder");
+            }
+
+            if (!holder.isBound() || holder.needsUpdate()) {
+                if (DEBUG) {
+                    Log.d(TAG, "getViewForPosition unbound holder or needs update; updating...");
+                }
+                adapter.bindViewHolder(holder, position);
+            }
+
+            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+            if (lp == null) {
+                lp = generateDefaultLayoutParams();
+                holder.itemView.setLayoutParams(lp);
+            } else if (!checkLayoutParams(lp)) {
+                lp = generateLayoutParams(lp);
+                holder.itemView.setLayoutParams(lp);
+            }
+            ((LayoutParams) lp).mViewHolder = holder;
+
+            return holder.itemView;
+        }
+
+        /**
+         * @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.
+         */
+        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.
+         *
+         * <p>A view must be fully detached before it may be recycled.</p>
+         *
+         * @param view Removed view for recycling
+         */
+        public void recycleView(View view) {
+            recycleViewHolder(getChildViewHolderInt(view));
+        }
+
+        void recycleViewHolder(ViewHolder holder) {
+            if (holder.isScrap() || holder.itemView.getParent() != null) {
+                throw new IllegalArgumentException(
+                        "Scrapped or attached views may not be recycled.");
+            }
+
+            // Retire oldest cached views first
+            if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
+                for (int i = 0; i < mCachedViews.size(); i++) {
+                    final ViewHolder cachedView = mCachedViews.get(i);
+                    if (cachedView.isRecyclable()) {
+                        mCachedViews.remove(i);
+                        getRecycledViewPool().putRecycledView(cachedView);
+                        dispatchViewRecycled(cachedView);
+                        break;
+                    }
+                }
+            }
+            if (mCachedViews.size() < mViewCacheMax && !holder.isRemoved()
+                    && !holder.isInvalid()) {
+                mCachedViews.add(holder);
+            } else if (holder.isRecyclable()) {
+                getRecycledViewPool().putRecycledView(holder);
+                dispatchViewRecycled(holder);
+            }
+        }
+
+        /**
+         * Used as a fast path for unscrapping and recycling a view during a bulk operation.
+         * The caller must call {@link #clearScrap()} when it's done to update the recycler's
+         * internal bookkeeping.
+         */
+        void quickRecycleScrapView(View view) {
+            final ViewHolder holder = getChildViewHolderInt(view);
+            holder.mScrapContainer = null;
+            recycleViewHolder(holder);
+        }
+
+        /**
+         * @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'.
+         */
+        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.
+         */
+        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
+         * for rebinding and reuse. Requests for a view for a given position may return a
+         * reused or rebound scrap view instance.</p>
+         *
+         * @param view View to scrap
+         */
+        void scrapView(View view) {
+            final ViewHolder holder = getChildViewHolderInt(view);
+            holder.setScrapContainer(this);
+            mAttachedScrap.add(holder);
+        }
+
+        /**
+         * Remove a previously scrapped view from the pool of eligible scrap.
+         *
+         * <p>This view will no longer be eligible for reuse until re-scrapped or
+         * until it is explicitly removed and recycled.</p>
+         */
+        void unscrapView(ViewHolder holder) {
+            mAttachedScrap.remove(holder);
+        }
+
+        /**
+         * @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.
+         */
+        public void detachDirtyScrapViews() {
+            mLayout.removeAndRecycleScrap(this);
+        }
+
+        int getScrapCount() {
+            return mAttachedScrap.size();
+        }
+
+        View getScrapViewAt(int index) {
+            return mAttachedScrap.get(index).itemView;
+        }
+
+        void clearScrap() {
+            mAttachedScrap.clear();
+        }
+
+        ViewHolder getScrapViewForPosition(int position, int type) {
+            final int scrapCount = mAttachedScrap.size();
+
+            // Try first for an exact, non-invalid match from scrap.
+            for (int i = 0; i < scrapCount; i++) {
+                final ViewHolder holder = mAttachedScrap.get(i);
+                if (holder.getPosition() == position && !holder.isInvalid() &&
+                        !holder.isRemoved()) {
+                    if (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 + ")");
+                        break;
+                    }
+                    mAttachedScrap.remove(i);
+                    holder.setScrapContainer(null);
+                    if (DEBUG) {
+                        Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
+                            ") found exact match in scrap: " + holder);
+                    }
+                    return holder;
+                }
+            }
+
+            // Search in our first-level recycled view cache.
+            final int cacheSize = mCachedViews.size();
+            for (int i = 0; i < cacheSize; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder.getPosition() == position) {
+                    mCachedViews.remove(i);
+                    if (holder.isInvalid() && holder.getItemViewType() != type) {
+                        // Can't use it. We don't know where it's been.
+                        if (DEBUG) {
+                            Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
+                                    ") found position match, but holder is invalid with type " +
+                                    holder.getItemViewType());
+                        }
+
+                        if (holder.isRecyclable()) {
+                            getRecycledViewPool().putRecycledView(holder);
+                        }
+                        // Even if the holder wasn't officially recycleable, dispatch that it
+                        // was recycled anyway in case there are resources to unbind.
+                        dispatchViewRecycled(holder);
+
+                        // Drop out of the cache search and try something else instead,
+                        // we won't find another match here.
+                        break;
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
+                                ") found match in cache: " + holder);
+                    }
+                    return holder;
+                }
+            }
+
+            // Give up. Head to the shared pool.
+            if (DEBUG) {
+                Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
+                    ") fetching from shared pool");
+            }
+            return getRecycledViewPool().getRecycledView(type);
+        }
+
+        ViewHolder getScrapViewForId(long id, int type) {
+            // Look in our attached views first
+            final int count = mAttachedScrap.size();
+            for (int i = 0; i < count; i++) {
+                final ViewHolder holder = mAttachedScrap.get(i);
+                if (holder.getItemId() == id) {
+                    if (type == holder.getItemViewType()) {
+                        mAttachedScrap.remove(i);
+                        holder.setScrapContainer(null);
+                        return holder;
+                    } else {
+                        break;
+                    }
+                }
+            }
+
+            // Search the first-level cache
+            final int cacheSize = mCachedViews.size();
+            for (int i = 0; i < cacheSize; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder.getItemId() == id) {
+                    mCachedViews.remove(i);
+                    return holder;
+                }
+            }
+
+            // That didn't work, look for an unordered view of the right type instead.
+            // The holder's position won't match so the calling code will need to have
+            // the adapter rebind it.
+            return getRecycledViewPool().getRecycledView(type);
+        }
+
+        void dispatchViewRecycled(ViewHolder holder) {
+            if (mRecyclerListener != null) {
+                mRecyclerListener.onViewRecycled(holder);
+            }
+            if (mAdapter != null) {
+                mAdapter.onViewRecycled(holder);
+            }
+            if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
+        }
+
+        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+            clear();
+            getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter);
+        }
+
+        void offsetPositionRecordsForInsert(int insertedAt, int count) {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null && holder.getPosition() >= insertedAt) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
+                                holder + " now at position " + (holder.mPosition + count));
+                    }
+                    holder.mPosition += count;
+                }
+            }
+        }
+
+        void offsetPositionRecordsForRemove(int removedFrom, int count) {
+            final int removedEnd = removedFrom + count;
+            final int cachedCount = mCachedViews.size();
+            for (int i = cachedCount - 1; i >= 0; i--) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null) {
+                    if (holder.getPosition() >= removedEnd) {
+                        if (DEBUG) {
+                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
+                                    " holder " + holder + " now at position " +
+                                    (holder.mPosition - count));
+                        }
+                        holder.mPosition -= count;
+                    } else if (holder.getPosition() >= removedFrom) {
+                        // Item for this view was removed. Dump it from the cache.
+                        if (DEBUG) {
+                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
+                                    " holder " + holder + " now placed in pool");
+                        }
+                        mCachedViews.remove(i);
+                        getRecycledViewPool().putRecycledView(holder);
+                        dispatchViewRecycled(holder);
+                    }
+                }
+            }
+        }
+
+        void setRecycledViewPool(RecycledViewPool pool) {
+            if (mRecyclerPool != null) {
+                mRecyclerPool.detach();
+            }
+            mRecyclerPool = pool;
+            if (pool != null) {
+                mRecyclerPool.attach(getAdapter());
+            }
+        }
+
+        RecycledViewPool getRecycledViewPool() {
+            if (mRecyclerPool == null) {
+                mRecyclerPool = new RecycledViewPool();
+            }
+            return mRecyclerPool;
+        }
+
+        ViewHolder findViewHolderForPosition(int position) {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null && holder.getPosition() == position) {
+                    mCachedViews.remove(i);
+                    return holder;
+                }
+            }
+            return null;
+        }
+
+        ViewHolder findViewHolderForItemId(long id) {
+            if (!mAdapter.hasStableIds()) {
+                return null;
+            }
+
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null && holder.getItemId() == id) {
+                    mCachedViews.remove(i);
+                    return holder;
+                }
+            }
+            return null;
+        }
+
+        void viewRangeUpdate(int positionStart, int itemCount) {
+            final int positionEnd = positionStart + itemCount;
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder == null) {
+                    continue;
+                }
+
+                final int pos = holder.getPosition();
+                if (pos >= positionStart && pos < positionEnd) {
+                    holder.addFlags(ViewHolder.FLAG_UPDATE);
+                }
+            }
+        }
+
+        void markKnownViewsInvalid() {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null) {
+                    holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+                }
+            }
+        }
+    }
+
+    /**
+     * Base class for an Adapter
+     *
+     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
+     * within a {@link RecyclerView}.</p>
+     */
+    public static abstract class Adapter<VH extends ViewHolder> {
+        private final AdapterDataObservable mObservable = new AdapterDataObservable();
+        private boolean mHasStableIds = false;
+
+        /** @deprecated */
+        private int mViewTypeCount = 1;
+
+        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+        public abstract void onBindViewHolder(VH holder, int position);
+
+        public final VH createViewHolder(ViewGroup parent, int viewType) {
+            final VH holder = onCreateViewHolder(parent, viewType);
+            holder.mItemViewType = viewType;
+            return holder;
+        }
+
+        public final void bindViewHolder(VH holder, int position) {
+            holder.mPosition = position;
+            if (hasStableIds()) {
+                holder.mItemId = getItemId(position);
+            }
+            onBindViewHolder(holder, position);
+            holder.setFlags(ViewHolder.FLAG_BOUND,
+                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+        }
+
+        /**
+         * Return the view type of the item at <code>position</code> for the purposes
+         * of view recycling.
+         *
+         * <p>The default implementation of this method returns 0, making the assumption of
+         * a single view type for the adapter. Unlike ListView adapters, types need not
+         * be contiguous. Consider using id resources to uniquely identify item view types.
+         *
+         * @param position position to query
+         * @return integer value identifying the type of the view needed to represent the item at
+         *                 <code>position</code>. Type codes need not be contiguous.
+         */
+        public int getItemViewType(int position) {
+            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.
+         */
+        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.
+         */
+        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 " +
+                        "stable IDs while the adapter has registered observers.");
+            }
+            mHasStableIds = hasStableIds;
+        }
+
+        /**
+         * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
+         * would return false this method should return {@link #NO_ID}. The default implementation
+         * of this method returns {@link #NO_ID}.
+         *
+         * @param position Adapter position to query
+         * @return the stable ID of the item at position
+         */
+        public long getItemId(int position) {
+            return NO_ID;
+        }
+
+        public abstract int getItemCount();
+
+        /**
+         * Returns true if this adapter publishes a unique <code>long</code> value that can
+         * act as a key for the item at a given position in the data set. If that item is relocated
+         * in the data set, the ID returned for that item should be the same.
+         *
+         * @return true if this adapter's items have stable IDs
+         */
+        public final boolean hasStableIds() {
+            return mHasStableIds;
+        }
+
+        /**
+         * Called when a view created by this adapter has been recycled.
+         *
+         * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
+         * needs to be attached to its parent {@link RecyclerView}. This can be because it has
+         * fallen out of visibility or a set of cached views represented by views still
+         * attached to the parent RecyclerView. If an item view has large or expensive data
+         * bound to it such as large bitmaps, this may be a good place to release those
+         * resources.</p>
+         *
+         * @param holder The ViewHolder for the view being recycled
+         */
+        public void onViewRecycled(VH holder) {
+        }
+
+        /**
+         * Called when a view created by this adapter has been attached to a window.
+         *
+         * <p>This can be used as a reasonable signal that the view is about to be seen
+         * by the user. If the adapter previously freed any resources in
+         * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
+         * those resources should be restored here.</p>
+         *
+         * @param holder Holder of the view being attached
+         */
+        public void onViewAttachedToWindow(VH holder) {
+        }
+
+        /**
+         * Called when a view created by this adapter has been detached from its window.
+         *
+         * <p>Becoming detached from the window is not necessarily a permanent condition;
+         * the consumer of an Adapter's views may choose to cache views offscreen while they
+         * are not visible, attaching an detaching them as appropriate.</p>
+         *
+         * @param holder Holder of the view being detached
+         */
+        public void onViewDetachedFromWindow(VH holder) {
+        }
+
+        /**
+         * Returns true if one or more observers are attached to this adapter.
+         * @return true if this adapter has observers
+         */
+        public final boolean hasObservers() {
+            return mObservable.hasObservers();
+        }
+
+        /**
+         * Register a new observer to listen for data changes.
+         *
+         * <p>The adapter may publish a variety of events describing specific changes.
+         * Not all adapters may support all change types and some may fall back to a generic
+         * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged()
+         * "something changed"} event if more specific data is not available.</p>
+         *
+         * <p>Components registering observers with an adapter are responsible for
+         * {@link #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         * unregistering} those observers when finished.</p>
+         *
+         * @param observer Observer to register
+         *
+         * @see #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         */
+        public void registerAdapterDataObserver(AdapterDataObserver observer) {
+            mObservable.registerObserver(observer);
+        }
+
+        /**
+         * Unregister an observer currently listening for data changes.
+         *
+         * <p>The unregistered observer will no longer receive events about changes
+         * to the adapter.</p>
+         *
+         * @param observer Observer to unregister
+         *
+         * @see #registerAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         */
+        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+            mObservable.unregisterObserver(observer);
+        }
+
+        /**
+         * Notify any registered observers that the data set has changed.
+         *
+         * <p>There are two different classes of data change events, item changes and structural
+         * changes. Item changes are when a single item has its data updated but no positional
+         * changes have occurred. Structural changes are when items are inserted, deleted or moved
+         * within the data set.</p>
+         *
+         * <p>This event does not specify what about the data set has changed, forcing
+         * any observers to assume that all existing items and structure may no longer be valid.
+         * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
+         *
+         * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
+         * for adapters that report that they have {@link #hasStableIds() stable IDs} when
+         * this method is used. This can help for the purposes of animation and visual
+         * object persistence but individual item views will still need to be rebound
+         * and relaid out.</p>
+         *
+         * <p>If you are writing an adapter it will always be more efficient to use the more
+         * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
+         * as a last resort.</p>
+         *
+         * @see #notifyItemChanged(int)
+         * @see #notifyItemInserted(int)
+         * @see #notifyItemRemoved(int)
+         * @see #notifyItemRangeChanged(int, int)
+         * @see #notifyItemRangeInserted(int, int)
+         * @see #notifyItemRangeRemoved(int, int)
+         */
+        public final void notifyDataSetChanged() {
+            mObservable.notifyChanged();
+        }
+
+        /**
+         * Notify any registered observers that the item at <code>position</code> has changed.
+         *
+         * <p>This is an item change event, not a structural change event. It indicates that any
+         * reflection of the data at <code>position</code> is out of date and should be updated.
+         * The item at <code>position</code> retains the same identity.</p>
+         *
+         * @param position Position of the item that has changed
+         *
+         * @see #notifyItemRangeChanged(int, int)
+         */
+        public final void notifyItemChanged(int position) {
+            mObservable.notifyItemRangeChanged(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the <code>itemCount</code> items starting at
+         * position <code>positionStart</code> have changed.
+         *
+         * <p>This is an item change event, not a structural change event. It indicates that
+         * any reflection of the data in the given position range is out of date and should
+         * be updated. The items in the given range retain the same identity.</p>
+         *
+         * @param positionStart Position of the first item that has changed
+         * @param itemCount Number of items that have changed
+         *
+         * @see #notifyItemChanged(int)
+         */
+        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeChanged(positionStart, itemCount);
+        }
+
+        /**
+         * Notify any registered observers that the item reflected at <code>position</code>
+         * has been newly inserted. The item previously at <code>position</code> is now at
+         * position <code>position + 1</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their
+         * positions may be altered.</p>
+         *
+         * @param position Position of the newly inserted item in the data set
+         *
+         * @see #notifyItemRangeInserted(int, int)
+         */
+        public final void notifyItemInserted(int position) {
+            mObservable.notifyItemRangeInserted(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the currently reflected <code>itemCount</code>
+         * items starting at <code>positionStart</code> have been newly inserted. The items
+         * previously located at <code>positionStart</code> and beyond can now be found starting
+         * at position <code>positionStart + itemCount</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param positionStart Position of the first item that was inserted
+         * @param itemCount Number of items inserted
+         *
+         * @see #notifyItemInserted(int)
+         */
+        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeInserted(positionStart, itemCount);
+        }
+
+        /**
+         * Notify any registered observers that the item previously located at <code>position</code>
+         * has been removed from the data set. The items previously located at and after
+         * <code>position</code> may now be found at <code>oldPosition - 1</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param position Position of the item that has now been removed
+         *
+         * @see #notifyItemRangeRemoved(int, int)
+         */
+        public final void notifyItemRemoved(int position) {
+            mObservable.notifyItemRangeRemoved(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the <code>itemCount</code> items previously
+         * located at <code>positionStart</code> have been removed from the data set. The items
+         * previously located at and after <code>positionStart + itemCount</code> may now be found
+         * at <code>oldPosition - itemCount</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the data
+         * set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param positionStart Previous position of the first item that was removed
+         * @param itemCount Number of items removed from the data set
+         */
+        public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+        }
+    }
+
+    /**
+     * A <code>LayoutManager</code> is responsible for measuring and positioning item views
+     * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
+     * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
+     * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
+     * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
+     * layout managers are provided for general use.
+     */
+    public static abstract class LayoutManager {
+        RecyclerView mRecyclerView;
+
+        @Nullable
+        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>
+         */
+        public final RecyclerView getRecyclerView() {
+            return mRecyclerView;
+        }
+
+        /**
+         * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
+         */
+        public void requestLayout() {
+            if(mRecyclerView != null) {
+                mRecyclerView.requestLayout();
+            }
+        }
+
+        /**
+         * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
+         * is attached to a window.
+         *
+         * <p>Subclass implementations should always call through to the superclass implementation.
+         * </p>
+         *
+         * @param view The RecyclerView this LayoutManager is bound to
+         */
+        public void onAttachedToWindow(RecyclerView view) {
+        }
+
+        /**
+         * Called when this LayoutManager is detached from its parent RecyclerView or when
+         * its parent RecyclerView is detached from its window.
+         *
+         * <p>Subclass implementations should always call through to the superclass implementation.
+         * </p>
+         *
+         * @param view The RecyclerView this LayoutManager is bound to
+         */
+        public void onDetachedFromWindow(RecyclerView view) {
+        }
+
+        /**
+         * @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);
+        }
+
+        /**
+         * Lay out all relevant child views from the given adapter.
+         *
+         * @param adapter          Adapter that will supply and bind views from a data set
+         * @param recycler         Recycler to use for fetching potentially cached views for a
+         *                         position
+         * @param structureChanged true if the structure of the data set has changed since
+         *                         the last call to onLayoutChildren, false otherwise
+         * @param state            Transient state of RecyclerView
+         */
+        public void onLayoutChildren(Adapter adapter, Recycler recycler, boolean structureChanged,
+                State state) {
+            Log.e(TAG, "You must override onLayoutChildren(Adapter adapter, Recycler recycler, "
+                    + "boolean structureChanged, State state)");
+            layoutChildren(adapter, recycler, structureChanged);
+        }
+
+        /**
+         * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
+         *
+         * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
+         * to store extra information specific to the layout. Client code should subclass
+         * {@link RecyclerView.LayoutParams} for this purpose.</p>
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @return A new LayoutParams for a child view
+         */
+        public abstract LayoutParams generateDefaultLayoutParams();
+
+        /**
+         * Determines the validity of the supplied LayoutParams object.
+         *
+         * <p>This should check to make sure that the object is of the correct type
+         * and all values are within acceptable ranges. The default implementation
+         * returns <code>true</code> for non-null params.</p>
+         *
+         * @param lp LayoutParams object to check
+         * @return true if this LayoutParams object is valid, false otherwise
+         */
+        public boolean checkLayoutParams(LayoutParams lp) {
+            return lp != null;
+        }
+
+        /**
+         * Create a LayoutParams object suitable for this LayoutManager, copying relevant
+         * values from the supplied LayoutParams object if possible.
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @param lp Source LayoutParams object to copy values from
+         * @return a new LayoutParams object
+         */
+        public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+            if (lp instanceof LayoutParams) {
+                return new LayoutParams((LayoutParams) lp);
+            } else if (lp instanceof MarginLayoutParams) {
+                return new LayoutParams((MarginLayoutParams) lp);
+            } else {
+                return new LayoutParams(lp);
+            }
+        }
+
+        /**
+         * Create a LayoutParams object suitable for this LayoutManager from
+         * an inflated layout resource.
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @param c Context for obtaining styled attributes
+         * @param attrs AttributeSet describing the supplied arguments
+         * @return a new LayoutParams object
+         */
+        public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+            return new LayoutParams(c, attrs);
+        }
+
+        /**
+         * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
+         * The default implementation does nothing and returns 0.
+         *
+         * @param dx            distance to scroll by in pixels. X increases as scroll position
+         *                      approaches the right.
+         * @param adapter       Adapter that will supply and bind views from a data set
+         * @param recycler      Recycler to use for fetching potentially cached views for a
+         *                      position
+         * @param state         Transient state of RecyclerView
+         * @return The actual distance scrolled. The return value will be negative if dx was
+         * negative and scrolling proceeeded in that direction.
+         * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
+         */
+        public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler,
+                State state) {
+            Log.e(TAG, "you must override "
+                    + "scrollHorizontallyBy(dx,adapter,recycler,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;
+        }
+
+        /**
+         * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
+         * The default implementation does nothing and returns 0.
+         *
+         * @param dy            distance to scroll in pixels. Y increases as scroll position
+         *                      approaches the bottom.
+         * @param adapter       Adapter that will supply and bind views from a data set
+         * @param recycler      Recycler to use for fetching potentially cached views for a
+         *                      position
+         * @param state         Transient state of RecyclerView
+         * @return The actual distance scrolled. The return value will be negative if dy was
+         * negative and scrolling proceeeded in that direction.
+         * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
+         */
+        public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler,
+                State state) {
+            Log.e(TAG, "you should override "
+                    + "scrollVerticallyBy(dx,adapter,recycler,state)");
+            return scrollVerticallyBy(dy, adapter, recycler);
+        }
+
+
+        /**
+         * @deprecated Use
+         * {@link #scrollVerticallyBy(int, Adapter, Recycler, RecyclerView.State)}
+         */
+        @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.
+         */
+        public int scrollVerticallyBy(int dy, Recycler recycler) {
+            return 0;
+        }
+
+        /**
+         * Query if horizontal scrolling is currently supported. The default implementation
+         * returns false.
+         *
+         * @return True if this LayoutManager can scroll the current contents horizontally
+         */
+        public boolean canScrollHorizontally() {
+            return false;
+        }
+
+        /**
+         * Query if vertical scrolling is currently supported. The default implementation
+         * returns false.
+         *
+         * @return True if this LayoutManager can scroll the current contents vertically
+         */
+        public boolean canScrollVertically() {
+            return false;
+        }
+
+        /**
+         * Scroll to the specified adapter position.
+         *
+         * Actual position of the item on the screen depends on the LayoutManager implementation.
+         * @param position Scroll to this adapter position.
+         */
+        public void scrollToPosition(int position) {
+            if (DEBUG) {
+                Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
+            }
+        }
+
+        /**
+         * <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)}.
+         * </p>
+         * @param recyclerView The RecyclerView to which this layout manager is attached
+         * @param adapter
+         * @param position Scroll to this adapter position.
+         */
+        public void smoothScrollToPosition(RecyclerView recyclerView, Adapter adapter,
+                int position) {
+            Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
+        }
+
+        /**
+         * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
+         * <p>Calling this method will cancel any previous smooth scroll request.</p>
+         * @param smoothScroller Unstance which defines how smooth scroll should be animated
+         */
+        public void startSmoothScroll(SmoothScroller smoothScroller) {
+            if (mSmoothScroller != null && smoothScroller != mSmoothScroller
+                    && mSmoothScroller.isRunning()) {
+                mSmoothScroller.stop();
+            }
+            mSmoothScroller = smoothScroller;
+            mSmoothScroller.start(mRecyclerView, this);
+        }
+
+        /**
+         * @return true if RecycylerView is currently in the state of smooth scrolling.
+         */
+        public boolean isSmoothScrolling() {
+            return mSmoothScroller != null && mSmoothScroller.isRunning();
+        }
+
+
+        /**
+         * Returns the resolved layout direction for this RecyclerView.
+         *
+         * @return {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout
+         * direction is RTL or returns
+         * {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction
+         * is not RTL.
+         */
+        public int getLayoutDirection() {
+            return ViewCompat.getLayoutDirection(mRecyclerView);
+        }
+
+        /**
+         * 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)}.
+         *
+         * @param child View to add
+         * @param index Index to add child at
+         */
+        public void addView(View child, int index) {
+            final ViewHolder holder = getChildViewHolderInt(child);
+            if (holder.isScrap()) {
+                holder.unScrap();
+                mRecyclerView.attachViewToParent(child, index, child.getLayoutParams());
+                if (DISPATCH_TEMP_DETACH) {
+                    ViewCompat.dispatchFinishTemporaryDetach(child);
+                }
+            } else {
+                mRecyclerView.addView(child, index);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                lp.mInsetsDirty = true;
+                final Adapter adapter = mRecyclerView.getAdapter();
+                if (adapter != null) {
+                    adapter.onViewAttachedToWindow(getChildViewHolderInt(child));
+                }
+                mRecyclerView.onChildAttachedToWindow(child);
+                if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
+                    mSmoothScroller.onChildAttachedToWindow(child);
+                }
+            }
+        }
+
+        /**
+         * 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)}.
+         *
+         * @param child View to add
+         */
+        public void addView(View child) {
+            addView(child, -1);
+        }
+
+        /**
+         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to completely remove a child view that is no longer needed.
+         * LayoutManagers should strongly consider recycling removed views using
+         * {@link Recycler#recycleView(android.view.View)}.
+         *
+         * @param child View to remove
+         */
+        public void removeView(View child) {
+            final Adapter adapter = mRecyclerView.getAdapter();
+            if (adapter != null) {
+                adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+            }
+            mRecyclerView.onChildDetachedFromWindow(child);
+            mRecyclerView.removeView(child);
+        }
+
+        /**
+         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to completely remove a child view that is no longer needed.
+         * LayoutManagers should strongly consider recycling removed views using
+         * {@link Recycler#recycleView(android.view.View)}.
+         *
+         * @param index Index of the child view to remove
+         */
+        public void removeViewAt(int index) {
+            final View child = mRecyclerView.getChildAt(index);
+            if (child != null) {
+                final Adapter adapter = mRecyclerView.getAdapter();
+                if (adapter != null) {
+                    adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+                }
+                mRecyclerView.onChildDetachedFromWindow(child);
+                mRecyclerView.removeViewAt(index);
+            }
+        }
+
+        /**
+         * Remove all views from the currently attached RecyclerView. This will not recycle
+         * any of the affected views; the LayoutManager is responsible for doing so if desired.
+         */
+        public void removeAllViews() {
+            final Adapter adapter = mRecyclerView.getAdapter();
+            if (adapter != null) {
+                final int childCount = mRecyclerView.getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    final View child = mRecyclerView.getChildAt(i);
+                    adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+                    mRecyclerView.onChildDetachedFromWindow(child);
+                }
+            }
+            mRecyclerView.removeAllViews();
+        }
+
+        /**
+         * Returns the adapter position of the item represented by the given View.
+         *
+         * @param view The view to query
+         * @return The adapter position of the item which is rendered by this View.
+         */
+        public int getPosition(View view) {
+            return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition();
+        }
+
+        /**
+         * <p>Finds the view which represents the given adapter position.</p>
+         * <p>This method traverses each child since it has no information about child order.
+         * Override this method to improve performance if your LayoutManager keeps data about
+         * child views.</p>
+         *
+         * @param position Position of the item in adapter
+         * @return The child view that represents the given position or null if the position is not
+         * visible
+         */
+        public View findViewByPosition(int position) {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (getPosition(child) == position) {
+                    return child;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Temporarily detach a child view.
+         *
+         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+         * views currently attached to the RecyclerView. Generally LayoutManager implementations
+         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+         * so that the detached view may be rebound and reused.</p>
+         *
+         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+         * before the LayoutManager entry point method called by RecyclerView returns.</p>
+         *
+         * @param child Child to detach
+         */
+        public void detachView(View child) {
+            if (DISPATCH_TEMP_DETACH) {
+                ViewCompat.dispatchStartTemporaryDetach(child);
+            }
+            mRecyclerView.detachViewFromParent(child);
+        }
+
+        /**
+         * Temporarily detach a child view.
+         *
+         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+         * views currently attached to the RecyclerView. Generally LayoutManager implementations
+         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+         * so that the detached view may be rebound and reused.</p>
+         *
+         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+         * before the LayoutManager entry point method called by RecyclerView returns.</p>
+         *
+         * @param index Index of the child to detach
+         */
+        public void detachViewAt(int index) {
+            if (DISPATCH_TEMP_DETACH) {
+                ViewCompat.dispatchStartTemporaryDetach(mRecyclerView.getChildAt(index));
+            }
+            mRecyclerView.detachViewFromParent(index);
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         * @param index Intended child index for child
+         * @param lp LayoutParams for child
+         */
+        public void attachView(View child, int index, LayoutParams lp) {
+            mRecyclerView.attachViewToParent(child, index, lp);
+            if (DISPATCH_TEMP_DETACH)  {
+                ViewCompat.dispatchFinishTemporaryDetach(child);
+            }
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         * @param index Intended child index for child
+         */
+        public void attachView(View child, int index) {
+            attachView(child, index, (LayoutParams) child.getLayoutParams());
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         */
+        public void attachView(View child) {
+            attachView(child, -1);
+        }
+
+        /**
+         * Finish removing a view that was previously temporarily
+         * {@link #detachView(android.view.View) detached}.
+         *
+         * @param child Detached child to remove
+         */
+        public void removeDetachedView(View child) {
+            mRecyclerView.removeDetachedView(child, false);
+        }
+
+        /**
+         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+         *
+         * <p>Scrapping a view allows it to be rebound and reused to show updated or
+         * different data.</p>
+         *
+         * @param child Child to detach and scrap
+         * @param recycler Recycler to deposit the new scrap view into
+         */
+        public void detachAndScrapView(View child, Recycler recycler) {
+            detachView(child);
+            recycler.scrapView(child);
+        }
+
+        /**
+         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+         *
+         * <p>Scrapping a view allows it to be rebound and reused to show updated or
+         * different data.</p>
+         *
+         * @param index Index of child to detach and scrap
+         * @param recycler Recycler to deposit the new scrap view into
+         */
+        public void detachAndScrapViewAt(int index, Recycler recycler) {
+            final View child = getChildAt(index);
+            detachViewAt(index);
+            recycler.scrapView(child);
+        }
+
+        /**
+         * Remove a child view and recycle it using the given Recycler.
+         *
+         * @param child Child to remove and recycle
+         * @param recycler Recycler to use to recycle child
+         */
+        public void removeAndRecycleView(View child, Recycler recycler) {
+            removeView(child);
+            recycler.recycleView(child);
+        }
+
+        /**
+         * Remove a child view and recycle it using the given Recycler.
+         *
+         * @param index Index of child to remove and recycle
+         * @param recycler Recycler to use to recycle child
+         */
+        public void removeAndRecycleViewAt(int index, Recycler recycler) {
+            final View view = getChildAt(index);
+            removeViewAt(index);
+            recycler.recycleView(view);
+        }
+
+        /**
+         * Return the current number of child views attached to the parent RecyclerView.
+         * This does not include child views that were temporarily detached and/or scrapped.
+         *
+         * @return Number of attached children
+         */
+        public int getChildCount() {
+            return mRecyclerView != null ? mRecyclerView.getChildCount() : 0;
+        }
+
+        /**
+         * Return the child view at the given index
+         * @param index Index of child to return
+         * @return Child view at index
+         */
+        public View getChildAt(int index) {
+            return mRecyclerView != null ? mRecyclerView.getChildAt(index) : null;
+        }
+
+        /**
+         * Return the width of the parent RecyclerView
+         *
+         * @return Width in pixels
+         */
+        public int getWidth() {
+            return mRecyclerView != null ? mRecyclerView.getWidth() : 0;
+        }
+
+        /**
+         * Return the height of the parent RecyclerView
+         *
+         * @return Height in pixels
+         */
+        public int getHeight() {
+            return mRecyclerView != null ? mRecyclerView.getHeight() : 0;
+        }
+
+        /**
+         * Return the left padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingLeft() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
+        }
+
+        /**
+         * Return the top padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingTop() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
+        }
+
+        /**
+         * Return the right padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingRight() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
+        }
+
+        /**
+         * Return the bottom padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingBottom() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
+        }
+
+        /**
+         * Return the start padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingStart() {
+            return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0;
+        }
+
+        /**
+         * Return the end padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingEnd() {
+            return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0;
+        }
+
+        /**
+         * Returns true if the RecyclerView this LayoutManager is bound to has focus.
+         *
+         * @return True if the RecyclerView has focus, false otherwise.
+         * @see View#isFocused()
+         */
+        public boolean isFocused() {
+            return mRecyclerView != null && mRecyclerView.isFocused();
+        }
+
+        /**
+         * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
+         *
+         * @return true if the RecyclerView has or contains focus
+         * @see View#hasFocus()
+         */
+        public boolean hasFocus() {
+            return mRecyclerView != null && mRecyclerView.hasFocus();
+        }
+
+        /**
+         * Return the number of items in the adapter bound to the parent RecyclerView
+         *
+         * @return Items in the bound adapter
+         */
+        public int getItemCount() {
+            final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
+            return a != null ? a.getItemCount() : 0;
+        }
+
+        /**
+         * Offset all child views attached to the parent RecyclerView by dx pixels along
+         * the horizontal axis.
+         *
+         * @param dx Pixels to offset by
+         */
+        public void offsetChildrenHorizontal(int dx) {
+            if (mRecyclerView != null) {
+                mRecyclerView.offsetChildrenHorizontal(dx);
+            }
+        }
+
+        /**
+         * Offset all child views attached to the parent RecyclerView by dy pixels along
+         * the vertical axis.
+         *
+         * @param dy Pixels to offset by
+         */
+        public void offsetChildrenVertical(int dy) {
+            if (mRecyclerView != null) {
+                mRecyclerView.offsetChildrenVertical(dy);
+            }
+        }
+
+        /**
+         * Temporarily detach and scrap all currently attached child views. Views will be scrapped
+         * into the given Recycler. The Recycler may prefer to reuse scrap views before
+         * other views that were previously recycled.
+         *
+         * @param recycler Recycler to scrap views into
+         */
+        public void detachAndScrapAttachedViews(Recycler recycler) {
+            final int childCount = getChildCount();
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View v = getChildAt(i);
+                detachViewAt(i);
+                recycler.scrapView(v);
+            }
+        }
+
+        /**
+         * Remove and recycle all scrap views currently tracked by Recycler. Recycled views
+         * will be made available for later reuse.
+         *
+         * @param recycler Recycler tracking scrap views to remove
+         */
+        public void removeAndRecycleScrap(Recycler recycler) {
+            final int scrapCount = recycler.getScrapCount();
+            for (int i = 0; i < scrapCount; i++) {
+                final View scrap = recycler.getScrapViewAt(i);
+                mRecyclerView.removeDetachedView(scrap, false);
+                recycler.quickRecycleScrapView(scrap);
+            }
+            recycler.clearScrap();
+        }
+
+        /**
+         * Measure a child view using standard measurement policy, taking the padding
+         * of the parent RecyclerView and any added item decorations into account.
+         *
+         * <p>If the RecyclerView can be scrolled in either dimension the caller may
+         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+         *
+         * @param child Child view to measure
+         * @param widthUsed Width in pixels currently consumed by other views, if relevant
+         * @param heightUsed Height in pixels currently consumed by other views, if relevant
+         */
+        public void measureChild(View child, int widthUsed, int heightUsed) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+            widthUsed += insets.left + insets.right;
+            heightUsed += insets.top + insets.bottom;
+
+            final int widthSpec = getChildMeasureSpec(getWidth(),
+                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
+                    canScrollHorizontally());
+            final int heightSpec = getChildMeasureSpec(getHeight(),
+                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
+                    canScrollVertically());
+            child.measure(widthSpec, heightSpec);
+        }
+
+        /**
+         * Measure a child view using standard measurement policy, taking the padding
+         * of the parent RecyclerView, any added item decorations and the child margins
+         * into account.
+         *
+         * <p>If the RecyclerView can be scrolled in either dimension the caller may
+         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+         *
+         * @param child Child view to measure
+         * @param widthUsed Width in pixels currently consumed by other views, if relevant
+         * @param heightUsed Height in pixels currently consumed by other views, if relevant
+         */
+        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+            widthUsed += insets.left + insets.right;
+            heightUsed += insets.top + insets.bottom;
+
+            final int widthSpec = getChildMeasureSpec(getWidth(),
+                    getPaddingLeft() + getPaddingRight() +
+                            lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
+                    canScrollHorizontally());
+            final int heightSpec = getChildMeasureSpec(getHeight(),
+                    getPaddingTop() + getPaddingBottom() +
+                            lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
+                    canScrollVertically());
+            child.measure(widthSpec, heightSpec);
+        }
+
+        /**
+         * Calculate a MeasureSpec value for measuring a child view in one dimension.
+         *
+         * @param parentSize Size of the parent view where the child will be placed
+         * @param padding Total space currently consumed by other elements of parent
+         * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+         *                       Generally obtained from the child view's LayoutParams
+         * @param canScroll true if the parent RecyclerView can scroll in this dimension
+         *
+         * @return a MeasureSpec value for the child view
+         */
+        public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
+                boolean canScroll) {
+            int size = Math.max(0, parentSize - padding);
+            int resultSize = 0;
+            int resultMode = 0;
+
+            if (canScroll) {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else {
+                    // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+                    // instead using UNSPECIFIED.
+                    resultSize = 0;
+                    resultMode = MeasureSpec.UNSPECIFIED;
+                }
+            } else {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.FILL_PARENT) {
+                    resultSize = size;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                    resultSize = size;
+                    resultMode = MeasureSpec.AT_MOST;
+                }
+            }
+            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+        }
+
+        /**
+         * Returns the measured width of the given child, plus the additional size of
+         * any insets applied by {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child view to query
+         * @return child's measured width plus <code>ItemDecoration</code> insets
+         *
+         * @see View#getMeasuredWidth()
+         */
+        public int getDecoratedMeasuredWidth(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getMeasuredWidth() + insets.left + insets.right;
+        }
+
+        /**
+         * Returns the measured height of the given child, plus the additional size of
+         * any insets applied by {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child view to query
+         * @return child's measured height plus <code>ItemDecoration</code> insets
+         *
+         * @see View#getMeasuredHeight()
+         */
+        public int getDecoratedMeasuredHeight(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getMeasuredHeight() + insets.top + insets.bottom;
+        }
+
+        /**
+         * Lay out the given child view within the RecyclerView using coordinates that
+         * include any current {@link ItemDecoration ItemDecorations}.
+         *
+         * <p>LayoutManagers should prefer working in sizes and coordinates that include
+         * item decoration insets whenever possible. This allows the LayoutManager to effectively
+         * ignore decoration insets within measurement and layout code. See the following
+         * methods:</p>
+         * <ul>
+         *     <li>{@link #measureChild(View, int, int)}</li>
+         *     <li>{@link #measureChildWithMargins(View, int, int)}</li>
+         *     <li>{@link #getDecoratedLeft(View)}</li>
+         *     <li>{@link #getDecoratedTop(View)}</li>
+         *     <li>{@link #getDecoratedRight(View)}</li>
+         *     <li>{@link #getDecoratedBottom(View)}</li>
+         *     <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+         *     <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+         * </ul>
+         *
+         * @param child Child to lay out
+         * @param left Left edge, with item decoration insets included
+         * @param top Top edge, with item decoration insets included
+         * @param right Right edge, with item decoration insets included
+         * @param bottom Bottom edge, with item decoration insets included
+         *
+         * @see View#layout(int, int, int, int)
+         */
+        public void layoutDecorated(View child, int left, int top, int right, int bottom) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            child.layout(left + insets.left, top + insets.top, right - insets.right,
+                    bottom - insets.bottom);
+        }
+
+        /**
+         * Returns the left edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child left edge with offsets applied
+         */
+        public int getDecoratedLeft(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getLeft() - insets.left;
+        }
+
+        /**
+         * Returns the top edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child top edge with offsets applied
+         */
+        public int getDecoratedTop(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getTop() - insets.top;
+        }
+
+        /**
+         * Returns the right edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child right edge with offsets applied
+         */
+        public int getDecoratedRight(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getRight() + insets.right;
+        }
+
+        /**
+         * Returns the bottom edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child bottom edge with offsets applied
+         */
+        public int getDecoratedBottom(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getBottom() + insets.bottom;
+        }
+
+        /**
+         * Called when searching for a focusable view in the given direction has failed
+         * for the current content of the RecyclerView.
+         *
+         * <p>This is the LayoutManager's opportunity to populate views in the given direction
+         * to fulfill the request if it can. The LayoutManager should attach and return
+         * the view to be focused. The default implementation returns null.</p>
+         *
+         * @param focused   The currently focused view
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         *                  or 0 for not applicable
+         * @param adapter   Adapter to use for obtaining new views
+         * @param recycler  The recycler to use for obtaining views for currently offscreen items
+         * @param state     Transient state of RecyclerView
+         * @return The chosen view to be focused
+         */
+        public View onFocusSearchFailed(View focused, int direction, Adapter adapter,
+                Recycler recycler, State state) {
+            Log.e(TAG, "You must override 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.
+         */
+        public View onFocusSearchFailed(View focused, int direction, Recycler recycler) {
+            return null;
+        }
+
+        /**
+         * 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)}
+         * 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.
+         *
+         * @param focused The currently focused view
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         * @return A descendant view to focus or null to fall back to default behavior.
+         *         The default implementation returns null.
+         */
+        public View onInterceptFocusSearch(View focused, int direction) {
+            return null;
+        }
+
+        /**
+         * @deprecated This method will be removed. Override {@link #requestChildRectangleOnScreen(
+         * RecyclerView, android.view.View, android.graphics.Rect, boolean)} instead.
+         */
+        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.
+         *
+         * <p>The base implementation will attempt to perform a standard programmatic scroll
+         * to bring the given rect into view, within the padded area of the RecyclerView.</p>
+         *
+         * @param child The direct child making the request.
+         * @param rect  The rectangle in the child's coordinates the child
+         *              wishes to be on the screen.
+         * @param immediate True to forbid animated or delayed scrolling,
+         *                  false otherwise
+         * @return Whether the group scrolled to handle the operation
+         */
+        public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
+                boolean immediate) {
+            final int parentLeft = getPaddingLeft();
+            final int parentTop = getPaddingTop();
+            final int parentRight = getWidth() - getPaddingRight();
+            final int parentBottom = getHeight() - getPaddingBottom();
+            final int childLeft = child.getLeft() + rect.left;
+            final int childTop = child.getTop() + rect.top;
+            final int childRight = childLeft + rect.right;
+            final int childBottom = childTop + rect.bottom;
+
+            final int offScreenLeft = Math.min(0, childLeft - parentLeft);
+            final int offScreenTop = Math.min(0, childTop - parentTop);
+            final int offScreenRight = Math.max(0, childRight - parentRight);
+            final int offScreenBottom = Math.max(0, childBottom - parentBottom);
+
+            // Favor the "start" layout direction over the end when bringing one side or the other
+            // of a large rect into view.
+            final int dx;
+            if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+                dx = offScreenRight != 0 ? offScreenRight : offScreenLeft;
+            } else {
+                dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight;
+            }
+
+            // Favor bringing the top into view over the bottom
+            final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;
+
+            if (dx != 0 || dy != 0) {
+                if (immediate) {
+                    parent.scrollBy(dx, dy);
+                } else {
+                    parent.smoothScrollBy(dx, dy);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Called when a descendant view of the RecyclerView requests focus.
+         *
+         * <p>A LayoutManager wishing to keep focused views aligned in a specific
+         * portion of the view may implement that behavior in an override of this method.</p>
+         *
+         * <p>If the LayoutManager executes different behavior that should override the default
+         * behavior of scrolling the focused child on screen instead of running alongside it,
+         * this method should return true.</p>
+         *
+         * @param parent The RecyclerView hosting this LayoutManager
+         * @param child Direct child of the RecyclerView containing the newly focused view
+         * @param focused The newly focused view. This may be the same view as child
+         * @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.
+         */
+        public boolean onRequestChildFocus(View child, View focused) {
+            return false;
+        }
+
+        /**
+         * @deprecated This method will be removed. Override
+         * {@link #onAdapterChanged(RecyclerView.Adapter, RecyclerView.Adapter)}
+         * instead.
+         */
+        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.
+         *
+         * <p>The default implementation removes all currently attached views.</p>
+         *
+         * @param oldAdapter The previous adapter instance. Will be null if there was previously no
+         *                   adapter.
+         * @param newAdapter The new adapter instance. Might be null if
+         *                   {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
+         */
+        public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+            onAdapterChanged();
+        }
+
+        /**
+         * Called to populate focusable views within the RecyclerView.
+         *
+         * <p>The LayoutManager implementation should return <code>true</code> if the default
+         * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be
+         * suppressed.</p>
+         *
+         * <p>The default implementation returns <code>false</code> to trigger RecyclerView
+         * to fall back to the default ViewGroup behavior.</p>
+         *
+         * @param recyclerView The RecyclerView hosting this LayoutManager
+         * @param views List of output views. This method should add valid focusable views
+         *              to this list.
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         * @param focusableMode The type of focusables to be added.
+         *
+         * @return true to suppress the default behavior, false to add default focusables after
+         *         this method returns.
+         *
+         * @see #FOCUSABLES_ALL
+         * @see #FOCUSABLES_TOUCH_MODE
+         */
+        public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
+                int direction, int focusableMode) {
+            return false;
+        }
+
+        /**
+         * Called when items have been added to the adapter. The LayoutManager may choose to
+         * requestLayout if the inserted items would require refreshing the currently visible set
+         * of child views. (e.g. currently empty space would be filled by appended items, etc.)
+         *
+         * @param recyclerView
+         * @param positionStart
+         * @param itemCount
+         */
+        public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        }
+
+        /**
+         * Called when items have been removed from the adapter.
+         *
+         * @param recyclerView
+         * @param positionStart
+         * @param itemCount
+         */
+        public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        }
+
+
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The horizontal extent of the scrollbar's thumb
+         * @see RecyclerView#computeHorizontalScrollExtent()
+         */
+        public int computeHorizontalScrollExtent(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The horizontal offset of the scrollbar's thumb
+         * @see RecyclerView#computeHorizontalScrollOffset()
+         */
+        public int computeHorizontalScrollOffset(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The total horizontal range represented by the vertical scrollbar
+         * @see RecyclerView#computeHorizontalScrollRange()
+         */
+        public int computeHorizontalScrollRange(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The total vertical range represented by the vertical scrollbar
+         * @see RecyclerView#computeVerticalScrollRange()
+         */
+        public int computeVerticalScrollRange(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
+         *
+         * * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The vertical offset of the scrollbar's thumb
+         * @see RecyclerView#computeVerticalScrollOffset()
+         */
+        public int computeVerticalScrollOffset(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param adapter Current adapter which is attached to RecyclerView
+         * @return The vertical extent of the scrollbar's thumb
+         * @see RecyclerView#computeVerticalScrollExtent()
+         */
+        public int computeVerticalScrollExtent(Adapter adapter) {
+            return 0;
+        }
+
+        /**
+         * Measure the attached RecyclerView. Implementations must call
+         * {@link #setMeasuredDimension(int, int)} before returning.
+         *
+         * <p>The default implementation will handle EXACTLY measurements and respect
+         * the minimum width and height properties of the host RecyclerView if measured
+         * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
+         * will consume all available space.</p>
+         *
+         * @param widthSpec Width {@link android.view.View.MeasureSpec}
+         * @param heightSpec Height {@link android.view.View.MeasureSpec}
+         */
+        public void onMeasure(int widthSpec, int heightSpec) {
+            final int widthMode = MeasureSpec.getMode(widthSpec);
+            final int heightMode = MeasureSpec.getMode(heightSpec);
+            final int widthSize = MeasureSpec.getSize(widthSpec);
+            final int heightSize = MeasureSpec.getSize(heightSpec);
+
+            int width = 0;
+            int height = 0;
+
+            switch (widthMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    width = widthSize;
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                default:
+                    width = getMinimumWidth();
+                    break;
+            }
+
+            switch (heightMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    height = heightSize;
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                default:
+                    height = getMinimumHeight();
+                    break;
+            }
+
+            setMeasuredDimension(width, height);
+        }
+
+        /**
+         * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the
+         * host RecyclerView.
+         *
+         * @param widthSize Measured width
+         * @param heightSize Measured height
+         */
+        public void setMeasuredDimension(int widthSize, int heightSize) {
+            mRecyclerView.setMeasuredDimension(widthSize, heightSize);
+        }
+
+        /**
+         * @return The host RecyclerView's {@link View#getMinimumWidth()}
+         */
+        public int getMinimumWidth() {
+            return ViewCompat.getMinimumWidth(mRecyclerView);
+        }
+
+        /**
+         * @return The host RecyclerView's {@link View#getMinimumHeight()}
+         */
+        public int getMinimumHeight() {
+            return ViewCompat.getMinimumHeight(mRecyclerView);
+        }
+        /**
+         * <p>Called when the LayoutManager should save its state. This is a good time to save your
+         * scroll position, configuration and anything else that may be required to restore the same
+         * layout state if the LayoutManager is recreated.</p>
+         * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and
+         * restore. This will let you share information between your LayoutManagers but it is also
+         * your responsibility to make sure they use the same parcelable class.</p>
+         *
+         * @return Necessary information for LayoutManager to be able to restore its state
+         */
+        public Parcelable onSaveInstanceState() {
+            return null;
+        }
+
+
+        public void onRestoreInstanceState(Parcelable state) {
+
+        }
+
+        void stopSmoothScroller() {
+            if (mSmoothScroller != null) {
+                mSmoothScroller.stop();
+            }
+        }
+
+        private void onSmoothScrollerStopped(SmoothScroller smoothScroller) {
+            if (mSmoothScroller == smoothScroller) {
+                mSmoothScroller = null;
+            }
+        }
+    }
+
+    /**
+     * An ItemDecoration allows the application to add a special drawing and layout offset
+     * to specific item views from the adapter's data set. This can be useful for drawing dividers
+     * between items, highlights, visual grouping boundaries and more.
+     *
+     * <p>All ItemDecorations are drawn in the order they were added, before the item
+     * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView) onDraw()} and after the items
+     * (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView)}.</p>
+     */
+    public static abstract class ItemDecoration {
+        /**
+         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+         * Any content drawn by this method will be drawn before the item views are drawn,
+         * and will thus appear underneath the views.
+         *
+         * @param c Canvas to draw into
+         * @param parent RecyclerView this ItemDecoration is drawing into
+         */
+        public void onDraw(Canvas c, RecyclerView parent) {
+        }
+
+        /**
+         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+         * Any content drawn by this method will be drawn after the item views are drawn
+         * and will thus appear over the views.
+         *
+         * @param c Canvas to draw into
+         * @param parent RecyclerView this ItemDecoration is drawing into
+         */
+        public void onDrawOver(Canvas c, RecyclerView parent) {
+        }
+
+        /**
+         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
+         * the number of pixels that the item view should be inset by, similar to padding or margin.
+         * The default implementation sets the bounds of outRect to 0 and returns.
+         *
+         * <p>If this ItemDecoration does not affect the positioning of item views it should set
+         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
+         * before returning.</p>
+         *
+         * @param outRect Rect to receive the output.
+         * @param itemPosition Adapter position of the item to offset
+         * @param parent RecyclerView this ItemDecoration is decorating
+         */
+        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
+            outRect.set(0, 0, 0, 0);
+        }
+    }
+
+    /**
+     * An OnItemTouchListener allows the application to intercept touch events in progress at the
+     * view hierarchy level of the RecyclerView before those touch events are considered for
+     * RecyclerView's own scrolling behavior.
+     *
+     * <p>This can be useful for applications that wish to implement various forms of gestural
+     * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
+     * a touch interaction already in progress even if the RecyclerView is already handling that
+     * gesture stream itself for the purposes of scrolling.</p>
+     */
+    public interface OnItemTouchListener {
+        /**
+         * Silently observe and/or take over touch events sent to the RecyclerView
+         * before they are handled by either the RecyclerView itself or its child views.
+         *
+         * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
+         * in the order in which each listener was added, before any other touch processing
+         * by the RecyclerView itself or child views occurs.</p>
+         *
+         * @param e MotionEvent describing the touch event. All coordinates are in
+         *          the RecyclerView's coordinate system.
+         * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
+         *         to continue with the current behavior and continue observing future events in
+         *         the gesture.
+         */
+        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
+
+        /**
+         * Process a touch event as part of a gesture that was claimed by returning true from
+         * a previous call to {@link #onInterceptTouchEvent}.
+         *
+         * @param e MotionEvent describing the touch event. All coordinates are in
+         *          the RecyclerView's coordinate system.
+         */
+        public void onTouchEvent(RecyclerView rv, MotionEvent e);
+    }
+
+    /**
+     * An OnScrollListener can be set on a RecyclerView to receive messages
+     * when a scrolling event has occurred on that RecyclerView.
+     *
+     * @see RecyclerView#setOnScrollListener(OnScrollListener)
+     */
+    public interface OnScrollListener {
+        public void onScrollStateChanged(int newState);
+        public void onScrolled(int dx, int dy);
+    }
+
+    /**
+     * A RecyclerListener can be set on a RecyclerView to receive messages whenever
+     * a view is recycled.
+     *
+     * @see RecyclerView#setRecyclerListener(RecyclerListener)
+     */
+    public interface RecyclerListener {
+
+        /**
+         * This method is called whenever the view in the ViewHolder is recycled.
+         *
+         * @param holder The ViewHolder containing the view that was recycled
+         */
+        public void onViewRecycled(ViewHolder holder);
+    }
+
+    /**
+     * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
+     *
+     * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
+     * potentially expensive {@link View#findViewById(int)} results.</p>
+     *
+     * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
+     * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
+     * their own custom ViewHolder implementations to store data that makes binding view contents
+     * easier. Implementations should assume that individual item views will hold strong references
+     * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
+     * strong references to extra off-screen item views for caching purposes</p>
+     */
+    public static abstract class ViewHolder {
+        public final View itemView;
+
+        int mPosition = NO_POSITION;
+        long mItemId = NO_ID;
+        int mItemViewType = INVALID_TYPE;
+
+        /**
+         * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
+         * are all valid.
+         */
+        static final int FLAG_BOUND = 1 << 0;
+
+        /**
+         * The data this ViewHolder's view reflects is stale and needs to be rebound
+         * by the adapter. mPosition and mItemId are consistent.
+         */
+        static final int FLAG_UPDATE = 1 << 1;
+
+        /**
+         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
+         * are not to be trusted and may no longer match the item view type.
+         * This ViewHolder must be fully rebound to different data.
+         */
+        static final int FLAG_INVALID = 1 << 2;
+
+        /**
+         * This ViewHolder points at data that represents an item previously removed from the
+         * data set. Its view may still be used for things like outgoing animations.
+         */
+        static final int FLAG_REMOVED = 1 << 3;
+
+        /**
+         * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
+         * and is intended to keep views around during animations.
+         */
+        static final int FLAG_NOT_RECYCLABLE = 1 << 4;
+
+        private int mFlags;
+
+        // If non-null, view is currently considered scrap and may be reused for other data by the
+        // scrap container.
+        private Recycler mScrapContainer = null;
+
+        public ViewHolder(View itemView) {
+            if (itemView == null) {
+                throw new IllegalArgumentException("itemView may not be null");
+            }
+            this.itemView = itemView;
+        }
+
+        public final int getPosition() {
+            return mPosition;
+        }
+
+        public final long getItemId() {
+            return mItemId;
+        }
+
+        public final int getItemViewType() {
+            return mItemViewType;
+        }
+
+        boolean isScrap() {
+            return mScrapContainer != null;
+        }
+
+        void unScrap() {
+            mScrapContainer.unscrapView(this);
+            mScrapContainer = null;
+        }
+
+        void setScrapContainer(Recycler recycler) {
+            mScrapContainer = recycler;
+        }
+
+        boolean isInvalid() {
+            return (mFlags & FLAG_INVALID) != 0;
+        }
+
+        boolean needsUpdate() {
+            return (mFlags & FLAG_UPDATE) != 0;
+        }
+
+        boolean isBound() {
+            return (mFlags & FLAG_BOUND) != 0;
+        }
+
+        boolean isRemoved() {
+            return (mFlags & FLAG_REMOVED) != 0;
+        }
+
+        void setFlags(int flags, int mask) {
+            mFlags = (mFlags & ~mask) | (flags & mask);
+        }
+
+        void addFlags(int flags) {
+            mFlags |= flags;
+        }
+
+        void clearFlagsForSharedPool() {
+            mFlags = 0;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ViewHolder{" +
+                    Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId);
+            if (isScrap()) sb.append(" scrap");
+            if (isInvalid()) sb.append(" invalid");
+            if (!isBound()) sb.append(" unbound");
+            if (needsUpdate()) sb.append(" update");
+            if (isRemoved()) sb.append(" removed");
+            sb.append("}");
+            return sb.toString();
+        }
+
+        public final void setIsRecyclable(boolean recyclable) {
+            // TODO: might want this to be a refcount instead
+            if (recyclable) {
+                mFlags &= ~FLAG_NOT_RECYCLABLE;
+            } else {
+                mFlags |= FLAG_NOT_RECYCLABLE;
+            }
+        }
+
+        public final boolean isRecyclable() {
+            return (mFlags & FLAG_NOT_RECYCLABLE) == 0 &&
+                    !ViewCompat.hasTransientState(itemView);
+        }
+    }
+
+    /**
+     * Queued operation to happen when child views are updated.
+     */
+    private static class UpdateOp {
+        public static final int ADD = 0;
+        public static final int REMOVE = 1;
+        public static final int UPDATE = 2;
+
+        static final int POOL_SIZE = 30;
+
+        public int cmd;
+        public int positionStart;
+        public int itemCount;
+
+        public UpdateOp(int cmd, int positionStart, int itemCount) {
+            this.cmd = cmd;
+            this.positionStart = positionStart;
+            this.itemCount = itemCount;
+        }
+    }
+
+    UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
+        UpdateOp op = mUpdateOpPool.acquire();
+        if (op == null) {
+            op = new UpdateOp(cmd, positionStart, itemCount);
+        } else {
+            op.cmd = cmd;
+            op.positionStart = positionStart;
+            op.itemCount = itemCount;
+        }
+        return op;
+    }
+
+    void recycleUpdateOp(UpdateOp op) {
+        mUpdateOpPool.release(op);
+    }
+
+    /**
+     * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
+     * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
+     * to create their own subclass of this <code>LayoutParams</code> class
+     * to store any additional required per-child view metadata about the layout.
+     */
+    public static class LayoutParams extends MarginLayoutParams {
+        ViewHolder mViewHolder;
+        final Rect mDecorInsets = new Rect();
+        boolean mInsetsDirty = true;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super((ViewGroup.LayoutParams) source);
+        }
+
+        /**
+         * Returns true if the view this LayoutParams is attached to needs to have its content
+         * updated from the corresponding adapter.
+         *
+         * @return true if the view should have its content updated
+         */
+        public boolean viewNeedsUpdate() {
+            return mViewHolder.needsUpdate();
+        }
+
+        /**
+         * Returns true if the view this LayoutParams is attached to is now representing
+         * potentially invalid data. A LayoutManager should scrap/recycle it.
+         *
+         * @return true if the view is invalid
+         */
+        public boolean isViewInvalid() {
+            return mViewHolder.isInvalid();
+        }
+
+        /**
+         * Returns true if the adapter data item corresponding to the view this LayoutParams
+         * is attached to has been removed from the data set. A LayoutManager may choose to
+         * treat it differently in order to animate its outgoing or disappearing state.
+         *
+         * @return true if the item the view corresponds to was removed from the data set
+         */
+        public boolean isItemRemoved() {
+            return mViewHolder.isRemoved();
+        }
+
+        /**
+         * Returns the position that the view this LayoutParams is attached to corresponds to.
+         *
+         * @return the adapter position this view was bound from
+         */
+        public int getViewPosition() {
+            return mViewHolder.getPosition();
+        }
+    }
+
+    /**
+     * Observer base class for watching changes to an {@link Adapter}.
+     * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
+     */
+    public static abstract class AdapterDataObserver {
+        public void onChanged() {
+            // Do nothing
+        }
+
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            // do nothing
+        }
+
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            // do nothing
+        }
+
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            // do nothing
+        }
+    }
+
+    /**
+     * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and
+     * provides methods to trigger a programmatic scroll.</p>
+     *
+     * @see LinearSmoothScroller
+     */
+    public static abstract class SmoothScroller {
+
+        private int mTargetPosition = RecyclerView.NO_POSITION;
+
+        private RecyclerView mRecyclerView;
+
+        private LayoutManager mLayoutManager;
+
+        private boolean mPendingInitialRun;
+
+        private boolean mRunning;
+
+        private View mTargetView;
+
+        private final Action mRecyclingAction;
+
+        public SmoothScroller() {
+            mRecyclingAction = new Action(0, 0);
+        }
+
+        /**
+         * Starts a smooth scroll for the given target position.
+         * <p>In each animation step, {@link RecyclerView} will check
+         * for the target view and call either
+         * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+         * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until
+         * SmoothScroller is stopped.</p>
+         *
+         * <p>Note that if RecyclerView finds the target view, it will automatically stop the
+         * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will
+         * stop calling SmoothScroller in each animation step.</p>
+         */
+        void start(RecyclerView recyclerView, LayoutManager layoutManager) {
+            mRecyclerView = recyclerView;
+            mLayoutManager = layoutManager;
+            if (mTargetPosition == RecyclerView.NO_POSITION) {
+                throw new IllegalArgumentException("Invalid target position");
+            }
+            mRecyclerView.mState.withTarget(mTargetPosition);
+            mRunning = true;
+            mPendingInitialRun = true;
+            mTargetView = findViewByPosition(getTargetPosition());
+            onStart();
+            mRecyclerView.mViewFlinger.postOnAnimation();
+        }
+
+        public void setTargetPosition(int targetPosition) {
+            mTargetPosition = targetPosition;
+        }
+
+        /**
+         * @return The LayoutManager to which this SmoothScroller is attached
+         */
+        public LayoutManager getLayoutManager() {
+            return mLayoutManager;
+        }
+
+        /**
+         * Stops running the SmoothScroller in each animation callback. Note that this does not
+         * cancel any existing {@link Action} updated by
+         * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+         * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
+         */
+        final protected void stop() {
+            if (mRunning) {
+                onStop();
+            }
+            mRecyclerView.mState.withTarget(RecyclerView.NO_POSITION);
+            mTargetView = null;
+            mTargetPosition = RecyclerView.NO_POSITION;
+            mPendingInitialRun = false;
+            mRunning = false;
+            // trigger a cleanup
+            mLayoutManager.onSmoothScrollerStopped(this);
+            // clear references to avoid any potential leak by a custom smooth scroller
+            mLayoutManager = null;
+            mRecyclerView = null;
+        }
+
+        /**
+         * Returns true if SmoothScroller has beens started but has not received the first
+         * animation
+         * callback yet.
+         *
+         * @return True if this SmoothScroller is waiting to start
+         */
+        public boolean isPendingInitialRun() {
+            return mPendingInitialRun;
+        }
+
+
+        /**
+         * @return True if SmoothScroller is currently active
+         */
+        public boolean isRunning() {
+            return mRunning;
+        }
+
+        /**
+         * Returns the adapter position of the target item
+         *
+         * @return Adapter position of the target item or
+         * {@link RecyclerView#NO_POSITION} if no target view is set.
+         */
+        public int getTargetPosition() {
+            return mTargetPosition;
+        }
+
+        private void onAnimation(int dx, int dy) {
+            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) {
+                stop();
+            }
+            mPendingInitialRun = false;
+            if (mTargetView != null) {
+                // verify target position
+                if (getChildPosition(mTargetView) == mTargetPosition) {
+                    onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction);
+                    mRecyclingAction.runInNecessary(mRecyclerView);
+                    stop();
+                } else {
+                    Log.e(TAG, "Passed over target position while smooth scrolling.");
+                    mTargetView = null;
+                }
+            }
+            if (mRunning) {
+                onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction);
+                mRecyclingAction.runInNecessary(mRecyclerView);
+            }
+        }
+
+        /**
+         * @see RecyclerView#getChildPosition(android.view.View)
+         */
+        public int getChildPosition(View view) {
+            return mRecyclerView.getChildPosition(view);
+        }
+
+        /**
+         * @see RecyclerView#getChildCount()
+         */
+        public int getChildCount() {
+            return mRecyclerView.getChildCount();
+        }
+
+        /**
+         * @see RecyclerView.LayoutManager#findViewByPosition(int)
+         */
+        public View findViewByPosition(int position) {
+            return mRecyclerView.mLayout.findViewByPosition(position);
+        }
+
+        /**
+         * @see RecyclerView#scrollToPosition(int)
+         */
+        public void instantScrollToPosition(int position) {
+            mRecyclerView.scrollToPosition(position);
+        }
+
+        protected void onChildAttachedToWindow(View child) {
+            if (getChildPosition(child) == getTargetPosition()) {
+                mTargetView = child;
+                if (DEBUG) {
+                    Log.d(TAG, "smooth scroll target view has been attached");
+                }
+            }
+        }
+
+        /**
+         * Normalizes the vector.
+         * @param scrollVector The vector that points to the target scroll position
+         */
+        protected void normalize(PointF scrollVector) {
+            final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y *
+                    scrollVector.y);
+            scrollVector.x /= magnitute;
+            scrollVector.y /= magnitute;
+        }
+
+        /**
+         * Called when smooth scroll is started. This might be a good time to do setup.
+         */
+        abstract protected void onStart();
+
+        /**
+         * Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
+         * @see #stop()
+         */
+        abstract protected void onStop();
+
+        /**
+         * <p>RecyclerView will call this method each time it scrolls until it can find the target
+         * position in the layout.</p>
+         * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the
+         * provided {@link Action} to define the next scroll.</p>
+         *
+         * @param dx        Last scroll amount horizontally
+         * @param dy        Last scroll amount verticaully
+         * @param state     Transient state of RecyclerView
+         * @param action    If you want to trigger a new smooth scroll and cancel the previous one,
+         *                  update this object.
+         */
+        abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action);
+
+        /**
+         * Called when the target position is laid out. This is the last callback SmoothScroller
+         * will receive and it should update the provided {@link Action} to define the scroll
+         * details towards the target view.
+         * @param targetView    The view element which render the target position.
+         * @param state         Transient state of RecyclerView
+         * @param action        Action instance that you should update to define final scroll action
+         *                      towards the targetView
+         * @return An {@link Action} to finalize the smooth scrolling
+         */
+        abstract protected void onTargetFound(View targetView, State state, Action action);
+
+        /**
+         * Holds information about a smooth scroll request by a {@link SmoothScroller}.
+         */
+        public static class Action {
+
+            public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
+
+            private int mDx;
+
+            private int mDy;
+
+            private int mDuration;
+
+            private Interpolator mInterpolator;
+
+            private boolean changed = false;
+
+            // we track this variable to inform custom implementer if they are updating the action
+            // in every animation callback
+            private int consecutiveUpdates = 0;
+
+            /**
+             * @param dx Pixels to scroll horizontally
+             * @param dy Pixels to scroll vertically
+             */
+            public Action(int dx, int dy) {
+                this(dx, dy, UNDEFINED_DURATION, null);
+            }
+
+            /**
+             * @param dx       Pixels to scroll horizontally
+             * @param dy       Pixels to scroll vertically
+             * @param duration Duration of the animation in milliseconds
+             */
+            public Action(int dx, int dy, int duration) {
+                this(dx, dy, duration, null);
+            }
+
+            /**
+             * @param dx           Pixels to scroll horizontally
+             * @param dy           Pixels to scroll vertically
+             * @param duration     Duration of the animation in milliseconds
+             * @param interpolator Interpolator to be used when calculating scroll position in each
+             *                     animation step
+             */
+            public Action(int dx, int dy, int duration, Interpolator interpolator) {
+                mDx = dx;
+                mDy = dy;
+                mDuration = duration;
+                mInterpolator = interpolator;
+            }
+            private void runInNecessary(RecyclerView recyclerView) {
+                if (changed) {
+                    validate();
+                    if (mInterpolator == null) {
+                        if (mDuration == UNDEFINED_DURATION) {
+                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
+                        } else {
+                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
+                        }
+                    } else {
+                        recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator);
+                    }
+                    consecutiveUpdates ++;
+                    if (consecutiveUpdates > 10) {
+                        // A new action is being set in every animation step. This looks like a bad
+                        // implementation. Inform developer.
+                        Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure"
+                                + " you are not changing it unless necessary");
+                    }
+                    changed = false;
+                } else {
+                    consecutiveUpdates = 0;
+                }
+            }
+
+            private void validate() {
+                if (mInterpolator != null && mDuration < 1) {
+                    throw new IllegalStateException("If you provide an interpolator, you must"
+                            + " set a positive duration");
+                } else if (mDuration < 1) {
+                    throw new IllegalStateException("Scroll duration must be a positive number");
+                }
+            }
+
+            public int getDx() {
+                return mDx;
+            }
+
+            public void setDx(int dx) {
+                changed = true;
+                mDx = dx;
+            }
+
+            public int getDy() {
+                return mDy;
+            }
+
+            public void setDy(int dy) {
+                changed = true;
+                mDy = dy;
+            }
+
+            public int getDuration() {
+                return mDuration;
+            }
+
+            public void setDuration(int duration) {
+                changed = true;
+                mDuration = duration;
+            }
+
+            public Interpolator getInterpolator() {
+                return mInterpolator;
+            }
+
+            /**
+             * Sets the interpolator to calculate scroll steps
+             * @param interpolator The interpolator to use. If you specify an interpolator, you must
+             *                     also set the duration.
+             * @see #setDuration(int)
+             */
+            public void setInterpolator(Interpolator interpolator) {
+                changed = true;
+                mInterpolator = interpolator;
+            }
+
+            /**
+             * Updates the action with given parameters.
+             * @param dx Pixels to scroll horizontally
+             * @param dy Pixels to scroll vertically
+             * @param duration Duration of the animation in milliseconds
+             * @param interpolator Interpolator to be used when calculating scroll position in each
+             *                     animation step
+             */
+            public void update(int dx, int dy, int duration, Interpolator interpolator) {
+                mDx = dx;
+                mDy = dy;
+                mDuration = duration;
+                mInterpolator = interpolator;
+                changed = true;
+            }
+        }
+    }
+
+    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+        public boolean hasObservers() {
+            return !mObservers.isEmpty();
+        }
+
+        public void notifyChanged() {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount) {
+            // since onItemRangeChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeInserted(int positionStart, int itemCount) {
+            // since onItemRangeInserted() is implemented by the app, it could do anything,
+            // including removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+            }
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+
+        Parcelable mLayoutState;
+
+        /**
+         * called by CREATOR
+         */
+        SavedState(Parcel in) {
+            super(in);
+            mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
+        }
+
+        /**
+         * Called by onSaveInstanceState
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeParcelable(mLayoutState, 0);
+        }
+
+        private void copyFrom(SavedState other) {
+            mLayoutState = other.mLayoutState;
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    /**
+     * <p>Contains useful information about the current RecyclerView state like target scroll
+     * position or view focus. State object can also keep arbitrary data, identified by resource
+     * ids.</p>
+     * <p>Often times, RecyclerView components will need to pass information between each other.
+     * To provide a well defined data bus between components, RecyclerView passes the same State
+     * object to component callbacks and these components can use it to exchange data.</p>
+     * <p>If you implement custom components, you can use State's put/get/remove methods to pass
+     * data between your components without needing to manage their lifecycles.</p>
+     */
+    public class State {
+
+        private int mTargetPosition = RecyclerView.NO_POSITION;
+
+        private SparseArray<Object> mData;
+
+        State reset() {
+            mTargetPosition = RecyclerView.NO_POSITION;
+            if (mData != null) {
+                mData.clear();
+            }
+            return this;
+        }
+
+        /**
+         * Removes the mapping from the specified id, if there was any.
+         * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to
+         *                   preserve cross functionality and avoid conflicts.
+         */
+        public void remove(int resourceId) {
+            if (mData == null) {
+                return;
+            }
+            mData.remove(resourceId);
+        }
+
+        /**
+         * Gets the Object mapped from the specified id, or <code>null</code>
+         * if no such data exists.
+         *
+         * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.*
+         *                   to
+         *                   preserve cross functionality and avoid conflicts.
+         */
+        public <T> T get(int resourceId) {
+            if (mData == null) {
+                return null;
+            }
+            return (T) mData.get(resourceId);
+        }
+
+        /**
+         * Adds a mapping from the specified id to the specified value, replacing the previous
+         * mapping from the specified key if there was one.
+         *
+         * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to
+         *                   preserve cross functionality and avoid conflicts.
+         * @param data       The data you want to associate with the resourceId.
+         */
+        public void put(int resourceId, Object data) {
+            if (mData == null) {
+                mData = new SparseArray<Object>();
+            }
+            mData.put(resourceId, data);
+        }
+
+        /**
+         * If scroll is triggered to make a certain item visible, this value will return the
+         * adapter index of that item.
+         * @return Adapter index of the target item or
+         * {@link RecyclerView#NO_POSITION} if there is no target
+         * position.
+         */
+        public int getTargetScrollPosition() {
+            return mTargetPosition;
+        }
+
+        /**
+         * Returns if current scroll has a target position.
+         * @return true if scroll is being triggered to make a certain position visible
+         * @see #getTargetScrollPosition()
+         */
+        public boolean hasTargetScrollPosition() {
+            return mTargetPosition != RecyclerView.NO_POSITION;
+        }
+
+        State withTarget(int targetPosition) {
+            mTargetPosition = targetPosition;
+            return this;
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewTest.java
new file mode 100644
index 0000000..eee2003
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.UUID;
+
+public class RecyclerViewTest extends AndroidTestCase {
+
+    RecyclerView mRecyclerView;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRecyclerView = new RecyclerView(mContext);
+    }
+
+    public void testMeasureWithoutLayoutManager() {
+        Throwable measureThrowable = null;
+        try {
+            measure();
+        } catch (Throwable throwable) {
+            measureThrowable = throwable;
+        }
+        assertTrue("Calling measure without a layout manager should throw exception"
+                , measureThrowable instanceof NullPointerException);
+    }
+
+    private void measure() {
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+    }
+
+    private void layout() {
+        mRecyclerView.layout(0, 0, 320, 320);
+    }
+
+    private void safeLayout() {
+        try {
+            layout();
+        } catch (Throwable t) {
+
+        }
+    }
+
+    public void testLayoutWithoutLayoutManager() throws InterruptedException {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        safeLayout();
+        assertEquals("layout manager should not be called if there is no adapter attached",
+                0, layoutManager.mLayoutCount);
+    }
+
+    public void testLayout() throws InterruptedException {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        layout();
+        assertEquals("when both layout manager and activity is set, recycler view should call"
+                + " layout manager's layout method", 1, layoutManager.mLayoutCount);
+    }
+
+    public void testObservingAdapters() {
+        MockAdapter adapterOld = new MockAdapter(1);
+        mRecyclerView.setAdapter(adapterOld);
+        assertTrue("attached adapter should have observables", adapterOld.hasObservers());
+
+        MockAdapter adapterNew = new MockAdapter(2);
+        mRecyclerView.setAdapter(adapterNew);
+        assertFalse("detached adapter should lose observable", adapterOld.hasObservers());
+        assertTrue("new adapter should have observers", adapterNew.hasObservers());
+
+        mRecyclerView.setAdapter(null);
+        assertNull("adapter should be removed successfully", mRecyclerView.getAdapter());
+        assertFalse("when adapter is removed, observables should be removed too",
+                adapterNew.hasObservers());
+    }
+
+    public void testAdapterChangeCallbacks() {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        MockAdapter adapterOld = new MockAdapter(1);
+        mRecyclerView.setAdapter(adapterOld);
+        layoutManager.assertPrevNextAdapters(null, adapterOld);
+
+        MockAdapter adapterNew = new MockAdapter(2);
+        mRecyclerView.setAdapter(adapterNew);
+        layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks"
+                , adapterOld, adapterNew);
+
+        mRecyclerView.setAdapter(null);
+        layoutManager.assertPrevNextAdapters(
+                "Setting adapter null should trigger correct callbacks",
+                adapterNew, null);
+    }
+
+    public void testSavedStateWithStatelessLayoutManager() throws InterruptedException {
+        mRecyclerView.setLayoutManager(new MockLayoutManager() {
+            @Override
+            public Parcelable onSaveInstanceState() {
+                return null;
+            }
+        });
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        Parcel parcel = Parcel.obtain();
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+
+        // reset position for reading
+        parcel.setDataPosition(0);
+        RecyclerView restored = new RecyclerView(mContext);
+        restored.setLayoutManager(new MockLayoutManager());
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        // restore
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        restored.onRestoreInstanceState(savedState);
+
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
+
+    }
+
+    public void testSavedState() throws InterruptedException {
+        MockLayoutManager mlm = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(mlm);
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        layout();
+        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);
+
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+
+        RecyclerView restored = new RecyclerView(mContext);
+        MockLayoutManager mlmRestored = new MockLayoutManager();
+        restored.setLayoutManager(mlmRestored);
+        restored.setAdapter(new MockAdapter(3));
+        restored.onRestoreInstanceState(savedState);
+
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
+        assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid,
+                mlmRestored.mUuid);
+        assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount,
+                mlmRestored.mLayoutCount);
+        layout();
+
+
+    }
+
+    static class MockLayoutManager extends RecyclerView.LayoutManager {
+
+        int mLayoutCount = 0;
+
+        int mAdapterChangedCount = 0;
+
+        RecyclerView.Adapter mPrevAdapter;
+
+        RecyclerView.Adapter mNextAdapter;
+
+        String mUuid = UUID.randomUUID().toString();
+
+        @Override
+        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+                RecyclerView.Adapter newAdapter) {
+            super.onAdapterChanged(oldAdapter, newAdapter);
+            mPrevAdapter = oldAdapter;
+            mNextAdapter = newAdapter;
+            mAdapterChangedCount++;
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler,
+                boolean structureChanged, RecyclerView.State state) {
+            mLayoutCount += 1;
+        }
+
+        @Override
+        public Parcelable onSaveInstanceState() {
+            LayoutManagerSavedState lss = new LayoutManagerSavedState();
+            lss.mUuid = mUuid;
+            return lss;
+        }
+
+        @Override
+        public void onRestoreInstanceState(Parcelable state) {
+            super.onRestoreInstanceState(state);
+            if (state instanceof LayoutManagerSavedState) {
+                mUuid = ((LayoutManagerSavedState) state).mUuid;
+            }
+        }
+
+        @Override
+        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
+                RecyclerView.Adapter nextAdapter) {
+            assertSame(message, prevAdapter, mPrevAdapter);
+            assertSame(message, nextAdapter, mNextAdapter);
+        }
+
+        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
+                RecyclerView.Adapter nextAdapter) {
+            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
+                    prevAdapter, nextAdapter);
+        }
+    }
+
+    static class LayoutManagerSavedState implements Parcelable {
+
+        String mUuid;
+
+        public LayoutManagerSavedState(Parcel in) {
+            mUuid = in.readString();
+        }
+
+        public LayoutManagerSavedState() {
+
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mUuid);
+        }
+
+        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
+                = new Parcelable.Creator<LayoutManagerSavedState>() {
+            @Override
+            public LayoutManagerSavedState createFromParcel(Parcel in) {
+                return new LayoutManagerSavedState(in);
+            }
+
+            @Override
+            public LayoutManagerSavedState[] newArray(int size) {
+                return new LayoutManagerSavedState[size];
+            }
+        };
+    }
+
+    static class MockAdapter extends RecyclerView.Adapter {
+
+        private int mCount = 0;
+
+        MockAdapter(int count) {
+            this.mCount = count;
+        }
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new MockViewHolder(new TextView(parent.getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+        }
+
+        @Override
+        public int getItemCount() {
+            return mCount;
+        }
+    }
+
+    static class MockViewHolder extends RecyclerView.ViewHolder {
+
+        public MockViewHolder(View itemView) {
+            super(itemView);
+        }
+    }
+
+}
+