Merge "Create v4 PathInterpolatorCompat" into lmp-mr1-ub-dev
diff --git a/design/base/android/support/design/widget/AnimationUtils.java b/design/base/android/support/design/widget/AnimationUtils.java
new file mode 100644
index 0000000..ad01cd5
--- /dev/null
+++ b/design/base/android/support/design/widget/AnimationUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+class AnimationUtils {
+
+    static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
+
+    /**
+     * Linear interpolation between {@code startValue} and {@code endValue} by {@code fraction}.
+     */
+    static float lerp(float startValue, float endValue, float fraction) {
+        return startValue + (fraction * (endValue - startValue));
+    }
+
+}
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
index 175e8b9..a725dd8 100644
--- a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -24,16 +24,12 @@
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.LayerDrawable;
 import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
 import android.view.View;
 import android.view.animation.Animation;
-import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
 
 class FloatingActionButtonEclairMr1 extends FloatingActionButtonImpl {
 
-    private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
-
     private Drawable mShapeDrawable;
     private Drawable mRippleDrawable;
 
@@ -149,7 +145,7 @@
     }
 
     private Animation setupAnimation(Animation animation) {
-        animation.setInterpolator(INTERPOLATOR);
+        animation.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
         animation.setDuration(mAnimationDuration);
         return animation;
     }
diff --git a/design/res/anim/snackbar_in.xml b/design/res/anim/snackbar_in.xml
new file mode 100644
index 0000000..a40524c
--- /dev/null
+++ b/design/res/anim/snackbar_in.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromYDelta="100%"
+           android:toYDelta="0"/>
diff --git a/design/res/anim/snackbar_out.xml b/design/res/anim/snackbar_out.xml
new file mode 100644
index 0000000..eb55cc0
--- /dev/null
+++ b/design/res/anim/snackbar_out.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromYDelta="0"
+           android:toYDelta="100%"/>
\ No newline at end of file
diff --git a/design/res/drawable/snackbar_background.xml b/design/res/drawable/snackbar_background.xml
new file mode 100644
index 0000000..739b516
--- /dev/null
+++ b/design/res/drawable/snackbar_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners android:radius="@dimen/snackbar_background_corner_radius"/>
+    <solid android:color="@color/snackbar_background_color"/>
+</shape>
\ No newline at end of file
diff --git a/design/res/layout-sw600dp/layout_snackbar.xml b/design/res/layout-sw600dp/layout_snackbar.xml
new file mode 100644
index 0000000..6605408
--- /dev/null
+++ b/design/res/layout-sw600dp/layout_snackbar.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+      class="android.support.design.widget.Snackbar$SnackbarLayout"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="bottom|center_vertical"
+      style="@style/Widget.Design.Snackbar" />
\ No newline at end of file
diff --git a/design/res/layout/layout_snackbar.xml b/design/res/layout/layout_snackbar.xml
new file mode 100644
index 0000000..604aafc
--- /dev/null
+++ b/design/res/layout/layout_snackbar.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+      class="android.support.design.widget.Snackbar$SnackbarLayout"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_gravity="bottom"
+      style="@style/Widget.Design.Snackbar" />
\ No newline at end of file
diff --git a/design/res/layout/layout_snackbar_include.xml b/design/res/layout/layout_snackbar_include.xml
new file mode 100644
index 0000000..0cf2002
--- /dev/null
+++ b/design/res/layout/layout_snackbar_include.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+            android:id="@+id/snackbar_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:paddingTop="@dimen/snackbar_padding_vertical"
+            android:paddingBottom="@dimen/snackbar_padding_vertical"
+            android:paddingLeft="@dimen/snackbar_padding_horizontal"
+            android:paddingRight="@dimen/snackbar_padding_horizontal"
+            android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
+            android:maxLines="@integer/snackbar_text_max_lines"
+            android:layout_gravity="center_vertical|left|start"
+            android:ellipsize="end"/>
+
+    <TextView
+            android:id="@+id/snackbar_action"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/snackbar_extra_spacing_horizontal"
+            android:layout_marginStart="@dimen/snackbar_extra_spacing_horizontal"
+            android:layout_gravity="center_vertical|right|end"
+            android:background="?attr/selectableItemBackground"
+            android:paddingTop="@dimen/snackbar_padding_vertical"
+            android:paddingBottom="@dimen/snackbar_padding_vertical"
+            android:paddingLeft="@dimen/snackbar_padding_horizontal"
+            android:paddingRight="@dimen/snackbar_padding_horizontal"
+            android:visibility="gone"
+            android:textAppearance="@style/TextAppearance.Design.Snackbar.Action"/>
+
+</merge>
\ No newline at end of file
diff --git a/design/res/values-sw600dp/config.xml b/design/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..baac13b
--- /dev/null
+++ b/design/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<resources>
+
+    <integer name="snackbar_text_max_lines">1</integer>
+
+</resources>
\ No newline at end of file
diff --git a/design/res/values-sw600dp/dimens.xml b/design/res/values-sw600dp/dimens.xml
index 2d966e6..37c3ff5 100644
--- a/design/res/values-sw600dp/dimens.xml
+++ b/design/res/values-sw600dp/dimens.xml
@@ -19,4 +19,11 @@
 
     <dimen name="tab_min_width">160dp</dimen>
 
+    <dimen name="snackbar_min_width">320dp</dimen>
+    <dimen name="snackbar_max_width">576dp</dimen>
+    <dimen name="snackbar_padding_vertical_2lines">@dimen/snackbar_padding_vertical</dimen>
+    <dimen name="snackbar_extra_spacing_horizontal">24dp</dimen>
+    <dimen name="snackbar_background_corner_radius">2dp</dimen>
+    <dimen name="snackbar_action_inline_max_width">0dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index 7a5582e..bf81b54 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -141,5 +141,21 @@
             <flag name="end" value="0x00800005" />
         </attr>
     </declare-styleable>
+
+    <declare-styleable name="TextInputLayout">
+        <attr name="hintTextAppearance" format="reference" />
+        <!-- The hint to display in the floating label -->
+        <attr name="android:hint" />
+        <!-- Whether the layout is laid out as if an error will be displayed -->
+        <attr name="errorEnabled" format="boolean" />
+        <!-- TextAppearance of any error message displayed -->
+        <attr name="errorTextAppearance" format="reference" />
+    </declare-styleable>
+
+    <declare-styleable name="SnackbarLayout">
+        <attr name="android:maxWidth" />
+        <attr name="maxActionInlineWidth" format="dimension" />
+    </declare-styleable>
+
 </resources>
 
diff --git a/design/res/values/colors.xml b/design/res/values/colors.xml
index fdd5127..f46e0ce 100644
--- a/design/res/values/colors.xml
+++ b/design/res/values/colors.xml
@@ -24,4 +24,8 @@
     <!-- Shadow color for the furthest pixels of a shadow -->
     <color name="shadow_end_color">@android:color/transparent</color>
 
+    <color name="error_color">#FFDD2C00</color>
+
+    <color name="snackbar_background_color">#323232</color>
+
 </resources>
\ No newline at end of file
diff --git a/design/res/values/config.xml b/design/res/values/config.xml
new file mode 100644
index 0000000..2ff276a
--- /dev/null
+++ b/design/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<resources>
+
+    <integer name="snackbar_text_max_lines">2</integer>
+
+</resources>
\ No newline at end of file
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index bb96119..23d9c0d 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -32,4 +32,21 @@
     <dimen name="tab_min_width">72dp</dimen>
     <dimen name="tab_max_width">264dp</dimen>
 
+    <dimen name="snackbar_min_width">-1px</dimen>
+    <dimen name="snackbar_max_width">-1px</dimen>
+    <dimen name="snackbar_elevation">2dp</dimen>
+    <dimen name="snackbar_background_corner_radius">0dp</dimen>
+
+    <dimen name="snackbar_padding_horizontal">12dp</dimen>
+    <dimen name="snackbar_padding_vertical">14dp</dimen>
+    <dimen name="snackbar_padding_vertical_2lines">24dp</dimen>
+
+    <!-- Extra spacing between the action and message views -->
+    <dimen name="snackbar_extra_spacing_horizontal">0dp</dimen>
+    <!-- The maximum width for a Snackbar's inline action. If the view is width than this then
+         the Snackbar will change to vertical stacking -->
+    <dimen name="snackbar_action_inline_max_width">128dp</dimen>
+
+    <dimen name="snackbar_text_size">14sp</dimen>
+
 </resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index f00293b..46e2144 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -59,5 +59,38 @@
         <item name="textAllCaps">true</item>
     </style>
 
+    <style name="Widget.Design.TextInputLayout" parent="android:Widget">
+        <item name="hintTextAppearance">@style/TextAppearance.Design.Hint</item>
+        <item name="errorTextAppearance">@style/TextAppearance.Design.Error</item>
+    </style>
+
+    <style name="TextAppearance.Design.Hint" parent="TextAppearance.AppCompat.Caption">
+        <item name="android:textColor">?attr/colorControlActivated</item>
+    </style>
+
+    <style name="TextAppearance.Design.Error" parent="TextAppearance.AppCompat.Caption">
+        <item name="android:textColor">@color/error_color</item>
+    </style>
+
+    <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance">
+        <item name="android:textSize">@dimen/snackbar_text_size</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Design.Snackbar.Action" parent="TextAppearance.AppCompat.Button">
+        <item name="android:textColor">?colorAccent</item>
+    </style>
+
+    <style name="Widget.Design.Snackbar" parent="android:Widget">
+        <item name="android:theme">@style/ThemeOverlay.AppCompat.Dark</item>
+        <item name="android:minWidth">@dimen/snackbar_min_width</item>
+        <item name="android:maxWidth">@dimen/snackbar_max_width</item>
+        <item name="android:background">@drawable/snackbar_background</item>
+        <item name="android:paddingLeft">@dimen/snackbar_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/snackbar_padding_horizontal</item>
+        <item name="android:elevation">@dimen/snackbar_elevation</item>
+        <item name="maxActionInlineWidth">@dimen/snackbar_action_inline_max_width</item>
+    </style>
+
 </resources>
 
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
new file mode 100644
index 0000000..2afe4b6
--- /dev/null
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.design.R;
+import android.support.v4.view.ViewCompat;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+final class CollapsingTextHelper {
+
+    // Pre-JB-MR2 doesn't support HW accelerated canvas scaled text so we will workaround it
+    // by using our own texture
+    private static final boolean USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18;
+
+    private static final boolean DEBUG_DRAW = false;
+    private static final Paint DEBUG_DRAW_PAINT;
+    static {
+        DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null;
+        if (DEBUG_DRAW_PAINT != null) {
+            DEBUG_DRAW_PAINT.setAntiAlias(true);
+            DEBUG_DRAW_PAINT.setColor(Color.MAGENTA);
+        }
+    }
+
+    private final View mView;
+
+    private float mExpandedFraction;
+
+    private final Rect mExpandedBounds;
+    private final Rect mCollapsedBounds;
+    private int mExpandedTextVerticalGravity = Gravity.CENTER_VERTICAL;
+    private int mCollapsedTextVerticalGravity = Gravity.CENTER_VERTICAL;
+    private float mExpandedTextSize;
+    private float mCollapsedTextSize;
+    private int mExpandedTextColor;
+    private int mCollapsedTextColor;
+
+    private float mExpandedTop;
+    private float mCollapsedTop;
+
+    private CharSequence mText;
+    private CharSequence mTextToDraw;
+    private float mTextWidth;
+
+    private boolean mUseTexture;
+    private Bitmap mExpandedTitleTexture;
+    private Paint mTexturePaint;
+    private float mTextureAscent;
+    private float mTextureDescent;
+
+    private float mCurrentLeft;
+    private float mCurrentRight;
+    private float mCurrentTop;
+    private float mScale;
+
+    private final TextPaint mTextPaint;
+
+    private Interpolator mPositionInterpolator;
+    private Interpolator mTextSizeInterpolator;
+
+    public CollapsingTextHelper(View view) {
+        mView = view;
+
+        mTextPaint = new TextPaint();
+        mTextPaint.setAntiAlias(true);
+
+        mCollapsedBounds = new Rect();
+        mExpandedBounds = new Rect();
+    }
+
+    void setTextSizeInterpolator(Interpolator interpolator) {
+        mTextSizeInterpolator = interpolator;
+        recalculate();
+    }
+
+    void setPositionInterpolator(Interpolator interpolator) {
+        mPositionInterpolator = interpolator;
+        recalculate();
+    }
+
+    void setExpandedTextSize(float textSize) {
+        if (mExpandedTextSize != textSize) {
+            mExpandedTextSize = textSize;
+            recalculate();
+        }
+    }
+
+    void setCollapsedTextSize(float textSize) {
+        if (mCollapsedTextSize != textSize) {
+            mCollapsedTextSize = textSize;
+            recalculate();
+        }
+    }
+
+    void setCollapsedTextColor(int textColor) {
+        if (mCollapsedTextColor != textColor) {
+            mCollapsedTextColor = textColor;
+            recalculate();
+        }
+    }
+
+    void setExpandedTextColor(int textColor) {
+        if (mExpandedTextColor != textColor) {
+            mExpandedTextColor = textColor;
+            recalculate();
+        }
+    }
+
+    void setExpandedBounds(int left, int top, int right, int bottom) {
+        mExpandedBounds.set(left, top, right, bottom);
+        recalculate();
+    }
+
+    void setCollapsedBounds(int left, int top, int right, int bottom) {
+        mCollapsedBounds.set(left, top, right, bottom);
+        recalculate();
+    }
+
+    void setExpandedTextVerticalGravity(int gravity) {
+        gravity &= Gravity.VERTICAL_GRAVITY_MASK;
+
+        if (mExpandedTextVerticalGravity != gravity) {
+            mExpandedTextVerticalGravity = gravity;
+            recalculate();
+        }
+    }
+
+    void setCollapsedTextVerticalGravity(int gravity) {
+        gravity &= Gravity.VERTICAL_GRAVITY_MASK;
+
+        if (mCollapsedTextVerticalGravity != gravity) {
+            mCollapsedTextVerticalGravity = gravity;
+            recalculate();
+        }
+    }
+
+    void setCollapsedTextAppearance(int resId) {
+        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
+        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+            mCollapsedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, 0);
+        }
+        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+            mCollapsedTextSize = a.getDimensionPixelSize(
+                    R.styleable.TextAppearance_android_textSize, 0);
+        }
+        a.recycle();
+
+        recalculate();
+    }
+
+    void setExpandedTextAppearance(int resId) {
+        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
+        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+            mExpandedTextColor = a.getColor(R.styleable.TextAppearance_android_textColor, 0);
+        }
+        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+            mExpandedTextSize = a.getDimensionPixelSize(
+                    R.styleable.TextAppearance_android_textSize, 0);
+        }
+        a.recycle();
+
+        recalculate();
+    }
+
+    /**
+     * Set the value indicating the current scroll value. This decides how much of the
+     * background will be displayed, as well as the title metrics/positioning.
+     *
+     * A value of {@code 0.0} indicates that the layout is fully expanded.
+     * A value of {@code 1.0} indicates that the layout is fully collapsed.
+     */
+    void setExpansionFraction(float fraction) {
+        if (fraction != mExpandedFraction) {
+            mExpandedFraction = fraction;
+            calculateOffsets();
+        }
+    }
+
+    float getExpansionFraction() {
+        return mExpandedFraction;
+    }
+
+    float getCollapsedTextSize() {
+        return mCollapsedTextSize;
+    }
+
+    float getExpandedTextSize() {
+        return mExpandedTextSize;
+    }
+
+    private void calculateOffsets() {
+        final float fraction = mExpandedFraction;
+
+        mCurrentLeft = interpolate(mExpandedBounds.left, mCollapsedBounds.left,
+                fraction, mPositionInterpolator);
+        mCurrentTop = interpolate(mExpandedTop, mCollapsedTop, fraction, mPositionInterpolator);
+        mCurrentRight = interpolate(mExpandedBounds.right, mCollapsedBounds.right,
+                fraction, mPositionInterpolator);
+        setInterpolatedTextSize(interpolate(mExpandedTextSize, mCollapsedTextSize,
+                fraction, mTextSizeInterpolator));
+
+        if (mCollapsedTextColor != mExpandedTextColor) {
+            // If the collapsed and expanded text colors are different, blend them based on the
+            // fraction
+            mTextPaint.setColor(blendColors(mExpandedTextColor, mCollapsedTextColor, fraction));
+        } else {
+            mTextPaint.setColor(mCollapsedTextColor);
+        }
+
+        ViewCompat.postInvalidateOnAnimation(mView);
+    }
+
+    private void calculateBaselines() {
+        // We then calculate the collapsed text size, using the same logic
+        mTextPaint.setTextSize(mCollapsedTextSize);
+        switch (mCollapsedTextVerticalGravity) {
+            case Gravity.BOTTOM:
+                mCollapsedTop = mCollapsedBounds.bottom;
+                break;
+            case Gravity.TOP:
+                mCollapsedTop = mCollapsedBounds.top - mTextPaint.ascent();
+                break;
+            case Gravity.CENTER_VERTICAL:
+            default:
+                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
+                float textOffset = (textHeight / 2) - mTextPaint.descent();
+                mCollapsedTop = mCollapsedBounds.centerY() + textOffset;
+                break;
+        }
+
+        mTextPaint.setTextSize(mExpandedTextSize);
+        switch (mExpandedTextVerticalGravity) {
+            case Gravity.BOTTOM:
+                mExpandedTop = mExpandedBounds.bottom;
+                break;
+            case Gravity.TOP:
+                mExpandedTop = mExpandedBounds.top - mTextPaint.ascent();
+                break;
+            case Gravity.CENTER_VERTICAL:
+            default:
+                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
+                float textOffset = (textHeight / 2) - mTextPaint.descent();
+                mExpandedTop = mExpandedBounds.centerY() + textOffset;
+                break;
+        }
+        mTextureAscent = mTextPaint.ascent();
+        mTextureDescent = mTextPaint.descent();
+
+        // The bounds have changed so we need to clear the texture
+        clearTexture();
+    }
+
+    public void draw(Canvas canvas) {
+        final int saveCount = canvas.save();
+
+        if (mTextToDraw != null) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(mView)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+            float x = isRtl ? mCurrentRight : mCurrentLeft;
+            float y = mCurrentTop;
+
+            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;
+
+            final float ascent;
+            final float descent;
+
+            if (drawTexture) {
+                ascent = mTextureAscent * mScale;
+                descent = mTextureDescent * mScale;
+            } else {
+                ascent = mTextPaint.ascent() * mScale;
+                descent = mTextPaint.descent() * mScale;
+            }
+
+            if (DEBUG_DRAW) {
+                // Just a debug tool, which drawn a Magneta rect in the text bounds
+                canvas.drawRect(mCurrentLeft, y + ascent, mCurrentRight, y + descent,
+                        DEBUG_DRAW_PAINT);
+            }
+
+            if (drawTexture) {
+                y += ascent;
+            }
+
+            if (mScale != 1f) {
+                canvas.scale(mScale, mScale, x, y);
+            }
+
+            if (isRtl) {
+                x -= mTextWidth;
+            }
+
+            if (drawTexture) {
+                // If we should use a texture, draw it instead of text
+                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
+            } else {
+                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
+            }
+        }
+
+        canvas.restoreToCount(saveCount);
+    }
+
+    private void setInterpolatedTextSize(final float textSize) {
+        if (mText == null) return;
+
+        boolean textSizeChanged;
+        float availableWidth;
+
+        if (isClose(textSize, mCollapsedTextSize)) {
+            textSizeChanged = mTextPaint.getTextSize() != mCollapsedTextSize;
+            mTextPaint.setTextSize(mCollapsedTextSize);
+            mScale = 1f;
+            availableWidth = mCollapsedBounds.width();
+        } else {
+            textSizeChanged = mTextPaint.getTextSize() != mExpandedTextSize;
+            mTextPaint.setTextSize(mExpandedTextSize);
+
+            if (isClose(textSize, mExpandedTextSize)) {
+                // If we're close to the expanded text size, snap to it and use a scale of 1
+                mScale = 1f;
+            } else {
+                // Else, we'll scale down from the expanded text size
+                mScale = textSize / mExpandedTextSize;
+            }
+            availableWidth = mExpandedBounds.width();
+        }
+
+        if (mTextToDraw == null || textSizeChanged) {
+            // If we don't currently have text to draw, or the text size has changed, ellipsize...
+            final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
+                    availableWidth, TextUtils.TruncateAt.END);
+            if (mTextToDraw == null || !mTextToDraw.equals(title)) {
+                mTextToDraw = title;
+            }
+            mTextWidth = mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length());
+        }
+
+        // Use our texture if the scale isn't 1.0
+        mUseTexture = USE_SCALING_TEXTURE && mScale != 1f;
+
+        if (mUseTexture) {
+            // Make sure we have an expanded texture if needed
+            ensureExpandedTexture();
+        }
+
+        ViewCompat.postInvalidateOnAnimation(mView);
+    }
+
+    private void ensureExpandedTexture() {
+        if (mExpandedTitleTexture != null || mExpandedBounds.isEmpty()
+                || TextUtils.isEmpty(mTextToDraw)) {
+            return;
+        }
+
+        mTextPaint.setTextSize(mExpandedTextSize);
+        mTextPaint.setColor(mExpandedTextColor);
+
+        final int w = Math.round(mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()));
+        final int h = Math.round(mTextPaint.descent() - mTextPaint.ascent());
+        mTextWidth = w;
+
+        if (w <= 0 && h <= 0) {
+            return; // If the width or height are 0, return
+        }
+
+        mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+
+        Canvas c = new Canvas(mExpandedTitleTexture);
+        c.drawText(mTextToDraw, 0, mTextToDraw.length(), 0, h - mTextPaint.descent(), mTextPaint);
+
+        if (mTexturePaint == null) {
+            // Make sure we have a paint
+            mTexturePaint = new Paint();
+            mTexturePaint.setAntiAlias(true);
+            mTexturePaint.setFilterBitmap(true);
+        }
+    }
+
+    private void recalculate() {
+        if (ViewCompat.isLaidOut(mView)) {
+            // If we've already been laid out, calculate everything now otherwise we'll wait
+            // until a layout
+            calculateBaselines();
+            calculateOffsets();
+        }
+    }
+
+    /**
+     * Set the title to display
+     *
+     * @param text
+     */
+    void setText(CharSequence text) {
+        if (text == null || !text.equals(mText)) {
+            mText = text;
+            clearTexture();
+            recalculate();
+        }
+    }
+
+    CharSequence getText() {
+        return mText;
+    }
+
+    private void clearTexture() {
+        if (mExpandedTitleTexture != null) {
+            mExpandedTitleTexture.recycle();
+            mExpandedTitleTexture = null;
+        }
+    }
+
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        recalculate();
+    }
+
+    /**
+     * Returns true if {@code value} is 'close' to it's closest decimal value. Close is currently
+     * defined as it's difference being < 0.001.
+     */
+    private static boolean isClose(float value, float targetValue) {
+        return Math.abs(value - targetValue) < 0.001f;
+    }
+
+    int getExpandedTextColor() {
+        return mExpandedTextColor;
+    }
+
+    int getCollapsedTextColor() {
+        return mCollapsedTextColor;
+    }
+
+    /**
+     * Blend {@code color1} and {@code color2} using the given ratio.
+     *
+     * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
+     *              1.0 will return {@code color2}.
+     */
+    private static int blendColors(int color1, int color2, float ratio) {
+        final float inverseRatio = 1f - ratio;
+        float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
+        float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
+        float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
+        float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
+        return Color.argb((int) a, (int) r, (int) g, (int) b);
+    }
+
+    private static float interpolate(float startValue, float endValue, float fraction,
+            Interpolator interpolator) {
+        if (interpolator != null) {
+            fraction = interpolator.getInterpolation(fraction);
+        }
+        return AnimationUtils.lerp(startValue, endValue, fraction);
+    }
+}
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index f943d55..d78562c 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -28,7 +28,6 @@
 import android.os.SystemClock;
 import android.support.design.R;
 import android.support.v4.view.GravityCompat;
-import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.NestedScrollingParent;
 import android.support.v4.view.NestedScrollingParentHelper;
 import android.support.v4.view.ViewCompat;
@@ -148,6 +147,7 @@
     private final Rect mTempRect1 = new Rect();
     private final Rect mTempRect2 = new Rect();
     private final Rect mTempRect3 = new Rect();
+    private final int[] mTempIntPair = new int[2];
     private Paint mScrimPaint;
 
     private boolean mIsAttachedToWindow;
@@ -155,6 +155,8 @@
     private int[] mKeylines;
 
     private View mBehaviorTouchView;
+    private View mNestedScrollingDirectChild;
+    private View mNestedScrollingTarget;
 
     private OnPreDrawListener mOnPreDrawListener;
     private boolean mNeedsPreDrawListener;
@@ -191,7 +193,7 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        resetBehaviors();
+        resetTouchBehaviors();
         if (mNeedsPreDrawListener) {
             if (mOnPreDrawListener == null) {
                 mOnPreDrawListener = new OnPreDrawListener();
@@ -205,11 +207,14 @@
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        resetBehaviors();
+        resetTouchBehaviors();
         if (mNeedsPreDrawListener && mOnPreDrawListener != null) {
             final ViewTreeObserver vto = getViewTreeObserver();
             vto.removeOnPreDrawListener(mOnPreDrawListener);
         }
+        if (mNestedScrollingTarget != null) {
+            onStopNestedScroll(mNestedScrollingTarget);
+        }
         mIsAttachedToWindow = false;
     }
 
@@ -219,7 +224,7 @@
      * in response to an UP or CANCEL event, when intercept is request-disallowed
      * and similar cases where an event stream in progress will be aborted.
      */
-    private void resetBehaviors() {
+    private void resetTouchBehaviors() {
         if (mBehaviorTouchView != null) {
             final Behavior b = ((LayoutParams) mBehaviorTouchView.getLayoutParams()).getBehavior();
             if (b != null) {
@@ -322,7 +327,7 @@
 
         // Make sure we reset in case we had missed a previous important event.
         if (action == MotionEvent.ACTION_DOWN) {
-            resetBehaviors();
+            resetTouchBehaviors();
         }
 
         final boolean intercepted = performIntercept(ev);
@@ -332,7 +337,7 @@
         }
 
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            resetBehaviors();
+            resetTouchBehaviors();
         }
 
         return intercepted;
@@ -377,7 +382,7 @@
         }
 
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            resetBehaviors();
+            resetTouchBehaviors();
         }
 
         return handled;
@@ -387,7 +392,7 @@
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
         if (disallowIntercept) {
-            resetBehaviors();
+            resetTouchBehaviors();
         }
     }
 
@@ -444,8 +449,12 @@
     LayoutParams getResolvedLayoutParams(View child) {
         final LayoutParams result = (LayoutParams) child.getLayoutParams();
         if (!result.mBehaviorResolved) {
-            final Class<?> childClass = child.getClass();
-            final DefaultBehavior defaultBehavior = childClass.getAnnotation(DefaultBehavior.class);
+            Class<?> childClass = child.getClass();
+            DefaultBehavior defaultBehavior = null;
+            while (childClass != null &&
+                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
+                childClass = childClass.getSuperclass();
+            }
             if (defaultBehavior != null) {
                 try {
                     result.setBehavior(defaultBehavior.value().newInstance());
@@ -957,7 +966,7 @@
             final Rect oldRect = mTempRect1;
             final Rect newRect = mTempRect2;
             getLastChildRect(child, oldRect);
-            getChildRect(child, false, newRect);
+            getChildRect(child, true, newRect);
             if (oldRect.equals(newRect)) {
                 continue;
             }
@@ -1100,6 +1109,27 @@
         return r.contains(x, y);
     }
 
+    /**
+     * Check whether two views overlap each other. The views need to be descendants of this
+     * {@link CoordinatorLayout} in the view hierarchy.
+     *
+     * @param first first child view to test
+     * @param second second child view to test
+     * @return true if both views are visible and overlap each other
+     */
+    public boolean doViewsOverlap(View first, View second) {
+        if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
+            final Rect firstRect = mTempRect1;
+            getChildRect(first, first.getParent() != this, firstRect);
+            final Rect secondRect = mTempRect2;
+            getChildRect(second, second.getParent() != this, secondRect);
+
+            return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
+                    || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+        }
+        return false;
+    }
+
     @Override
     public LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new LayoutParams(getContext(), attrs);
@@ -1126,37 +1156,151 @@
     }
 
     public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
-        // TODO
-        return false;
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
+                        nestedScrollAxes);
+                handled |= accepted;
+
+                lp.acceptNestedScroll(accepted);
+            } else {
+                lp.acceptNestedScroll(false);
+            }
+        }
+        return handled;
     }
 
     public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
         mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
-        // TODO
+        mNestedScrollingDirectChild = child;
+        mNestedScrollingTarget = target;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
+            }
+        }
     }
 
     public void onStopNestedScroll(View target) {
         mNestedScrollingParentHelper.onStopNestedScroll(target);
-        // TODO
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onStopNestedScroll(this, view, target);
+            }
+            lp.resetNestedScroll();
+        }
+
+        mNestedScrollingDirectChild = null;
+        mNestedScrollingTarget = null;
     }
 
     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
             int dxUnconsumed, int dyUnconsumed) {
-        // TODO
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed);
+            }
+        }
     }
 
     public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
-        // TODO
+        int xConsumed = 0;
+        int yConsumed = 0;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                mTempIntPair[0] = mTempIntPair[1] = 0;
+                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
+
+                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
+                        : Math.min(xConsumed, mTempIntPair[0]);
+                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
+                        : Math.min(yConsumed, mTempIntPair[1]);
+            }
+        }
+
+        consumed[0] = xConsumed;
+        consumed[1] = yConsumed;
     }
 
     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
-        // TODO
-        return false;
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
+                        consumed);
+            }
+        }
+        return handled;
     }
 
     public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
-        // TODO
-        return false;
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted()) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
+            }
+        }
+        return handled;
     }
 
     public int getNestedScrollAxes() {
@@ -1469,6 +1613,208 @@
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             return lp.mBehaviorTag;
         }
+
+
+        /**
+         * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
+         * to this event and return true to indicate that the CoordinatorLayout should act as
+         * a nested scrolling parent for this scroll. Only Behaviors that return true from
+         * this method will receive subsequent nested scroll events.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param directTargetChild the child view of the CoordinatorLayout that either is or
+         *                          contains the target of the nested scroll operation
+         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
+         * @param nestedScrollAxes the axes that this nested scroll applies to. See
+         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
+         * @return true if the Behavior wishes to accept this nested scroll
+         *
+         * @see NestedScrollingParent#onStartNestedScroll(View, View, int)
+         */
+        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
+                V child, View directTargetChild, View target, int nestedScrollAxes) {
+            return false;
+        }
+
+        /**
+         * Called when a nested scroll has been accepted by the CoordinatorLayout.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param directTargetChild the child view of the CoordinatorLayout that either is or
+         *                          contains the target of the nested scroll operation
+         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
+         * @param nestedScrollAxes the axes that this nested scroll applies to. See
+         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
+         *
+         * @see NestedScrollingParent#onNestedScrollAccepted(View, View, int)
+         */
+        public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
+                View directTargetChild, View target, int nestedScrollAxes) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll has ended.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event
+         * sequence. This is a good place to clean up any state related to the nested scroll.
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout that initiated
+         *               the nested scroll
+         *
+         * @see NestedScrollingParent#onStopNestedScroll(View)
+         */
+        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll in progress has updated and the target has scrolled or
+         * attempted to scroll.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
+         * nested scrolling child, with both consumed and unconsumed components of the scroll
+         * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
+         * same values.</em>
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
+         * @param dyConsumed vertical pixels consumed by the target's own scrolling operation
+         * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
+         *                     operation, but requested by the user
+         * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
+         *                     but requested by the user
+         *
+         * @see NestedScrollingParent#onNestedScroll(View, int, int, int, int)
+         */
+        public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
+                int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll in progress is about to update, before the target has
+         * consumed any of the scrolled distance.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
+         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
+         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
+         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
+         * of pixels in either direction that any Behavior responding to the nested scroll reported
+         * as consumed.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param dx the raw horizontal number of pixels that the user attempted to scroll
+         * @param dy the raw vertical number of pixels that the user attempted to scroll
+         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
+         *                 was consumed, consumed[1] should be set to the distance of dy that
+         *                 was consumed
+         *
+         * @see NestedScrollingParent#onNestedPreScroll(View, int, int, int[])
+         */
+        public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
+                int dx, int dy, int[] consumed) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scrolling child is starting a fling or an action that would
+         * be a fling.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedFling</code> is called when the current nested scrolling child view
+         * detects the proper conditions for a fling. It reports if the child itself consumed
+         * the fling. If it did not, the child is expected to show some sort of overscroll
+         * indication. This method should return true if it consumes the fling, so that a child
+         * that did not itself take an action in response can choose not to show an overfling
+         * indication.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param velocityX horizontal velocity of the attempted fling
+         * @param velocityY vertical velocity of the attempted fling
+         * @param consumed true if the nested child view consumed the fling
+         * @return true if the Behavior consumed the fling
+         *
+         * @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
+         */
+        public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
+                float velocityX, float velocityY, boolean consumed) {
+            return false;
+        }
+
+        /**
+         * Called when a nested scrolling child is about to start a fling.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
+         * detects the proper conditions for a fling, but it has not acted on it yet. A
+         * Behavior can return true to indicate that it consumed the fling. If at least one
+         * Behavior returns true, the fling should not be acted upon by the child.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param velocityX horizontal velocity of the attempted fling
+         * @param velocityY vertical velocity of the attempted fling
+         * @return true if the Behavior consumed the fling
+         *
+         * @see NestedScrollingParent#onNestedPreFling(View, float, float)
+         */
+        public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
+                float velocityX, float velocityY) {
+            return false;
+        }
     }
 
     /**
@@ -1511,7 +1857,8 @@
         View mAnchorView;
         View mAnchorDirectChild;
 
-        boolean mDidBlockInteraction;
+        private boolean mDidBlockInteraction;
+        private boolean mDidAcceptNestedScroll;
 
         final Rect mLastChildRect = new Rect();
 
@@ -1686,6 +2033,18 @@
             mDidBlockInteraction = false;
         }
 
+        void resetNestedScroll() {
+            mDidAcceptNestedScroll = false;
+        }
+
+        void acceptNestedScroll(boolean accept) {
+            mDidAcceptNestedScroll = accept;
+        }
+
+        boolean isNestedScrollAccepted() {
+            return mDidAcceptNestedScroll;
+        }
+
         /**
          * Check if an associated child view depends on another child view of the CoordinatorLayout.
          *
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 0ea5878..d51ae1d 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -26,12 +26,18 @@
 import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.design.R;
+import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.widget.ImageView;
 
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
 /**
  * Floating action buttons are used for a special type of promoted action. They are distinguished
  * by a circled icon floating above the UI and have special motion behaviors related to morphing,
@@ -41,6 +47,7 @@
  * the mini, which should only be used to create visual continuity with other elements on the
  * screen.
  */
+@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
 public class FloatingActionButton extends ImageView {
 
     // These values must match those in the attrs declaration
@@ -281,7 +288,7 @@
      * our parent's edges.
      */
     private void updateOffset() {
-        if (mShadowPadding.width() > 0 && mShadowPadding.height() > 0) {
+        if (mShadowPadding.centerX() != 0 || mShadowPadding.centerY() != 0) {
             int offsetTB = 0, offsetLR = 0;
 
             if (isOnRightParentEdge()) {
@@ -333,4 +340,97 @@
         }
         return false;
     }
+
+    /**
+     * Behavior designed for use with {@link FloatingActionButton} instances. It's main function
+     * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
+     * not cover them.
+     */
+    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
+        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
+        // because we can use view translation properties which greatly simplifies the code.
+        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
+
+        private float mTranslationY;
+        private Set<WeakReference<View>> mSnackbars;
+
+        @Override
+        public boolean layoutDependsOn(CoordinatorLayout parent,
+                FloatingActionButton child,
+                View dependency) {
+            // We're dependent on all SnackbarLayouts
+            if (SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout) {
+                if (!containsView(dependency)) {
+                    if (mSnackbars == null) mSnackbars = new HashSet<>();
+                    mSnackbars.add(new WeakReference<>(dependency));
+                }
+                cleanUpSet();
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
+                View snackbar) {
+            updateFabTranslation(parent, child, snackbar);
+            return false;
+        }
+
+        private void updateFabTranslation(CoordinatorLayout parent, FloatingActionButton fab,
+                View snackbar) {
+            final float translationY = getTranslationYForFab(parent, fab);
+            if (translationY != mTranslationY) {
+                // First, cancel any current animation
+                ViewCompat.animate(fab).cancel();
+
+                if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) {
+                    // If we're travelling by the height of the Snackbar then we probably need to
+                    // animate to the value
+                    ViewCompat.animate(fab).translationY(translationY)
+                            .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
+                } else {
+                    // Else we'll set use setTranslationY
+                    ViewCompat.setTranslationY(fab, translationY);
+                }
+                mTranslationY = translationY;
+            }
+        }
+
+        private float getTranslationYForFab(CoordinatorLayout parent, FloatingActionButton fab) {
+            float minOffset = 0;
+            if (mSnackbars != null && !mSnackbars.isEmpty()) {
+                for (WeakReference<View> viewRef : mSnackbars) {
+                    final View view = viewRef.get();
+                    if (view != null && parent.doViewsOverlap(fab, view)) {
+                        minOffset = Math.min(minOffset,
+                                ViewCompat.getTranslationY(view) - view.getHeight());
+                    }
+                }
+            }
+            return minOffset;
+        }
+
+        private void cleanUpSet() {
+            if (mSnackbars != null && !mSnackbars.isEmpty()) {
+                for (final Iterator<WeakReference<View>> i = mSnackbars.iterator(); i.hasNext();) {
+                    WeakReference<View> ref = i.next();
+                    if (ref == null || ref.get() == null) {
+                        i.remove();
+                    }
+                }
+            }
+        }
+
+        private boolean containsView(View dependency) {
+            if (mSnackbars != null && !mSnackbars.isEmpty()) {
+                for (WeakReference<View> viewRef : mSnackbars) {
+                    if (viewRef.get() == dependency) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
 }
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
new file mode 100644
index 0000000..041985c
--- /dev/null
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.IntDef;
+import android.support.annotation.StringRes;
+import android.support.design.R;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR;
+
+/**
+ * Snackbars provide lightweight feedback about an operation. They show a brief message at the
+ * bottom of the screen on mobile and lower left on larger devices. Snackbars appear above all other
+ * elements on screen and only one can be displayed at a time.
+ * <p>
+ * They automatically disappear after a timeout or after user interaction elsewhere on the screen,
+ * particularly after interactions that summon a new surface or activity. Snackbars can be swiped
+ * off screen.
+ * <p>
+ * Snackbars can contain an action which is set via
+ * {@link #setAction(CharSequence, android.view.View.OnClickListener)}.
+ */
+public class Snackbar {
+
+    /**
+     * @hide
+     */
+    @IntDef({LENGTH_SHORT, LENGTH_LONG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Duration {}
+
+    /**
+     * Show the Snackbar for a short period of time.
+     *
+     * @see #setDuration
+     */
+    public static final int LENGTH_SHORT = -1;
+
+    /**
+     * Show the Snackbar for a long period of time.
+     *
+     * @see #setDuration
+     */
+    public static final int LENGTH_LONG = 0;
+
+    private static final int ANIMATION_DURATION = 250;
+    private static final int ANIMATION_FADE_DURATION = 180;
+
+    private static final Handler sHandler;
+    private static final int MSG_SHOW = 0;
+    private static final int MSG_DISMISS = 1;
+
+    static {
+        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_SHOW:
+                        ((Snackbar) message.obj).showView();
+                        return true;
+                    case MSG_DISMISS:
+                        ((Snackbar) message.obj).hideView();
+                        return true;
+                }
+                return false;
+            }
+        });
+    }
+
+    private final ViewGroup mParent;
+    private final Context mContext;
+    private final SnackbarLayout mView;
+    private int mDuration;
+
+    Snackbar(ViewGroup parent) {
+        mParent = parent;
+        mContext = parent.getContext();
+
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mView = (SnackbarLayout) inflater.inflate(R.layout.layout_snackbar, mParent, false);
+    }
+
+    /**
+     * Make a Snackbar to display a message.
+     *
+     * @param parent   The parent to add Snackbars to.
+     * @param text     The text to show.  Can be formatted text.
+     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or {@link
+     *                 #LENGTH_LONG}
+     */
+    public static Snackbar make(ViewGroup parent, CharSequence text, @Duration int duration) {
+        Snackbar snackbar = new Snackbar(parent);
+        snackbar.setText(text);
+        snackbar.setDuration(duration);
+        return snackbar;
+    }
+
+    /**
+     * Make a Snackbar to display a message.
+     *
+     * @param parent   The parent to add Snackbars to.
+     * @param resId    The resource id of the string resource to use.  Can be formatted text.
+     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or {@link
+     *                 #LENGTH_LONG}
+     */
+    public static Snackbar make(ViewGroup parent, int resId, @Duration int duration) {
+        return make(parent, parent.getResources().getText(resId), duration);
+    }
+
+    /**
+     * Set the action to be displayed in this {@link Snackbar}.
+     *
+     * @param resId    String resource to display
+     * @param listener callback to be invoked when the action is clicked
+     */
+    public Snackbar setAction(@StringRes int resId, View.OnClickListener listener) {
+        return setAction(mContext.getText(resId), listener);
+    }
+
+    /**
+     * Set the action to be displayed in this {@link Snackbar}.
+     *
+     * @param text     Text to display
+     * @param listener callback to be invoked when the action is clicked
+     */
+    public Snackbar setAction(CharSequence text, final View.OnClickListener listener) {
+        final TextView tv = mView.getActionView();
+
+        if (TextUtils.isEmpty(text) || listener == null) {
+            tv.setVisibility(View.GONE);
+            tv.setOnClickListener(null);
+        } else {
+            tv.setVisibility(View.VISIBLE);
+            tv.setText(text);
+            tv.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    listener.onClick(view);
+
+                    // Now dismiss the Snackbar
+                    dismiss();
+                }
+            });
+        }
+        return this;
+    }
+
+    /**
+     * Update the text in this {@link Snackbar}.
+     *
+     * @param message The new text for the Toast.
+     */
+    public Snackbar setText(CharSequence message) {
+        final TextView tv = mView.getMessageView();
+        tv.setText(message);
+        return this;
+    }
+
+    /**
+     * Update the text in this {@link Snackbar}.
+     *
+     * @param resId The new text for the Toast.
+     */
+    public Snackbar setText(@StringRes int resId) {
+        return setText(mContext.getText(resId));
+    }
+
+    /**
+     * Set how long to show the view for.
+     *
+     * @param duration either be one of the predefined lengths:
+     *                 {@link #LENGTH_SHORT}, {@link #LENGTH_LONG}, or a custom duration
+     *                 in milliseconds.
+     */
+    public Snackbar setDuration(@Duration int duration) {
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Return the duration.
+     *
+     * @see #setDuration
+     */
+    @Duration
+    public int getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Returns the {@link Snackbar}'s view.
+     */
+    public View getView() {
+        return mView;
+    }
+
+    /**
+     * Show the {@link Snackbar}.
+     */
+    public void show() {
+        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
+    }
+
+    /**
+     * Dismiss the {@link Snackbar}.
+     */
+    public void dismiss() {
+        SnackbarManager.getInstance().dismiss(mManagerCallback);
+    }
+
+    private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
+        @Override
+        public void show() {
+            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
+        }
+
+        @Override
+        public void dismiss() {
+            sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, Snackbar.this));
+        }
+    };
+
+    final void showView() {
+        if (mView.getParent() == null) {
+            final ViewGroup.LayoutParams lp = mView.getLayoutParams();
+
+            if (lp instanceof CoordinatorLayout.LayoutParams) {
+                // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
+
+                final Behavior behavior = new Behavior();
+                behavior.setStartAlphaSwipeDistance(0.1f);
+                behavior.setEndAlphaSwipeDistance(0.6f);
+                behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
+                behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
+                    @Override
+                    public void onDismiss(View view) {
+                        dismiss();
+                    }
+
+                    @Override
+                    public void onDragStateChanged(int state) {
+                        switch (state) {
+                            case SwipeDismissBehavior.STATE_DRAGGING:
+                            case SwipeDismissBehavior.STATE_SETTLING:
+                                // If the view is being dragged or settling, cancel the timeout
+                                SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
+                                break;
+                            case SwipeDismissBehavior.STATE_IDLE:
+                                // If the view has been released and is idle, restore the timeout
+                                SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
+                                break;
+                        }
+                    }
+                });
+                ((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
+            }
+
+            mParent.addView(mView);
+        }
+
+        if (ViewCompat.isLaidOut(mView)) {
+            // If the view is already laid out, animate it now
+            animateViewIn();
+        } else {
+            // Otherwise, add one of our layout change listeners and animate it in when laid out
+            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
+                    animateViewIn();
+                    mView.setOnLayoutChangeListener(null);
+                }
+            });
+        }
+    }
+
+    private void animateViewIn() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            ViewCompat.setTranslationY(mView, mView.getHeight());
+            ViewCompat.animate(mView).translationY(0f)
+                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
+                    .setDuration(ANIMATION_DURATION)
+                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(View view) {
+                            mView.animateChildrenIn(ANIMATION_DURATION - ANIMATION_FADE_DURATION,
+                                    ANIMATION_FADE_DURATION);
+                        }
+
+                        @Override
+                        public void onAnimationEnd(View view) {
+                            SnackbarManager.getInstance().onShown(mManagerCallback);
+                        }
+                    }).start();
+        } else {
+            Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.snackbar_in);
+            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
+            anim.setDuration(ANIMATION_DURATION);
+            anim.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    SnackbarManager.getInstance().onShown(mManagerCallback);
+                }
+
+                @Override
+                public void onAnimationStart(Animation animation) {}
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {}
+            });
+            mView.startAnimation(anim);
+        }
+    }
+
+    private void animateViewOut() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            ViewCompat.animate(mView).translationY(mView.getHeight())
+                    .setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
+                    .setDuration(ANIMATION_DURATION)
+                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(View view) {
+                            mView.animateChildrenOut(0, ANIMATION_FADE_DURATION);
+                        }
+
+                        @Override
+                        public void onAnimationEnd(View view) {
+                            onViewHidden();
+                        }
+                    }).start();
+        } else {
+            Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.snackbar_out);
+            anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
+            anim.setDuration(ANIMATION_DURATION);
+            anim.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    onViewHidden();
+                }
+
+                @Override
+                public void onAnimationStart(Animation animation) {}
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {}
+            });
+            mView.startAnimation(anim);
+        }
+    }
+
+    final void hideView() {
+        if (mView.getVisibility() != View.VISIBLE || isBeingDragged()) {
+            onViewHidden();
+        } else {
+            animateViewOut();
+        }
+    }
+
+    private void onViewHidden() {
+        // First remove the view from the parent
+        mParent.removeView(mView);
+        // Now, tell the SnackbarManager that it has been dismissed
+        SnackbarManager.getInstance().onDismissed(mManagerCallback);
+    }
+
+    /**
+     * @return if the view is being being dragged or settled by {@link SwipeDismissBehavior}.
+     */
+    private boolean isBeingDragged() {
+        final ViewGroup.LayoutParams lp = mView.getLayoutParams();
+
+        if (lp instanceof CoordinatorLayout.LayoutParams) {
+            final CoordinatorLayout.LayoutParams cllp = (CoordinatorLayout.LayoutParams) lp;
+            final CoordinatorLayout.Behavior behavior = cllp.getBehavior();
+
+            if (behavior instanceof SwipeDismissBehavior) {
+                return ((SwipeDismissBehavior) behavior).getDragState()
+                        != SwipeDismissBehavior.STATE_IDLE;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public static class SnackbarLayout extends LinearLayout {
+        private TextView mMessageView;
+        private TextView mActionView;
+
+        private int mMaxWidth;
+        private int mMaxInlineActionWidth;
+
+        interface OnLayoutChangeListener {
+            public void onLayoutChange(View view, int left, int top, int right, int bottom);
+        }
+
+        private OnLayoutChangeListener mOnLayoutChangeListener;
+
+        public SnackbarLayout(Context context) {
+            this(context, null);
+        }
+
+        public SnackbarLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SnackbarLayout);
+            mMaxWidth = a.getDimensionPixelSize(R.styleable.SnackbarLayout_android_maxWidth, -1);
+            mMaxInlineActionWidth = a.getDimensionPixelSize(
+                    R.styleable.SnackbarLayout_maxActionInlineWidth, -1);
+            a.recycle();
+
+            setClickable(true);
+
+            // Now inflate our content. We need to do this manually rather than using an <include>
+            // in the layout since older versions of the Android do not inflate includes with
+            // the correct Context.
+            LayoutInflater.from(context).inflate(R.layout.layout_snackbar_include, this);
+        }
+
+        @Override
+        protected void onFinishInflate() {
+            super.onFinishInflate();
+            mMessageView = (TextView) findViewById(R.id.snackbar_text);
+            mActionView = (TextView) findViewById(R.id.snackbar_action);
+        }
+
+        TextView getMessageView() {
+            return mMessageView;
+        }
+
+        TextView getActionView() {
+            return mActionView;
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            if (mMaxWidth > 0 && getMeasuredWidth() > mMaxWidth) {
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+
+            final int multiLineVPadding = getResources().getDimensionPixelSize(
+                    R.dimen.snackbar_padding_vertical_2lines);
+            final int singleLineVPadding = getResources().getDimensionPixelSize(
+                    R.dimen.snackbar_padding_vertical);
+            final boolean isMultiLine = mMessageView.getLayout().getLineCount() > 1;
+
+            boolean remeasure = false;
+            if (isMultiLine && mMaxInlineActionWidth > 0
+                    && mActionView.getMeasuredWidth() > mMaxInlineActionWidth) {
+                if (updateViewsWithinLayout(VERTICAL, multiLineVPadding,
+                        multiLineVPadding - singleLineVPadding)) {
+                    remeasure = true;
+                }
+            } else {
+                final int messagePadding = isMultiLine ? multiLineVPadding : singleLineVPadding;
+                if (updateViewsWithinLayout(HORIZONTAL, messagePadding, messagePadding)) {
+                    remeasure = true;
+                }
+            }
+
+            if (remeasure) {
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+        }
+
+        void animateChildrenIn(int delay, int duration) {
+            ViewCompat.setAlpha(mMessageView, 0f);
+            ViewCompat.animate(mMessageView).alpha(1f).setDuration(duration)
+                    .setStartDelay(delay).start();
+
+            if (mActionView.getVisibility() == VISIBLE) {
+                ViewCompat.setAlpha(mActionView, 0f);
+                ViewCompat.animate(mActionView).alpha(1f).setDuration(duration)
+                        .setStartDelay(delay).start();
+            }
+        }
+
+        void animateChildrenOut(int delay, int duration) {
+            ViewCompat.setAlpha(mMessageView, 1f);
+            ViewCompat.animate(mMessageView).alpha(0f).setDuration(duration)
+                    .setStartDelay(delay).start();
+
+            if (mActionView.getVisibility() == VISIBLE) {
+                ViewCompat.setAlpha(mActionView, 1f);
+                ViewCompat.animate(mActionView).alpha(0f).setDuration(duration)
+                        .setStartDelay(delay).start();
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            super.onLayout(changed, l, t, r, b);
+            if (changed && mOnLayoutChangeListener != null) {
+                mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b);
+            }
+        }
+
+        void setOnLayoutChangeListener(OnLayoutChangeListener onLayoutChangeListener) {
+            mOnLayoutChangeListener = onLayoutChangeListener;
+        }
+
+        private boolean updateViewsWithinLayout(final int orientation,
+                final int messagePadTop, final int messagePadBottom) {
+            boolean changed = false;
+            if (orientation != getOrientation()) {
+                setOrientation(orientation);
+                changed = true;
+            }
+            if (mMessageView.getPaddingTop() != messagePadTop
+                    || mMessageView.getPaddingBottom() != messagePadBottom) {
+                updateTopBottomPadding(mMessageView, messagePadTop, messagePadBottom);
+                changed = true;
+            }
+            return changed;
+        }
+
+        private static void updateTopBottomPadding(View view, int topPadding, int bottomPadding) {
+            if (ViewCompat.isPaddingRelative(view)) {
+                ViewCompat.setPaddingRelative(view,
+                        ViewCompat.getPaddingStart(view), topPadding,
+                        ViewCompat.getPaddingEnd(view), bottomPadding);
+            } else {
+                view.setPadding(view.getPaddingLeft(), topPadding,
+                        view.getPaddingRight(), bottomPadding);
+            }
+        }
+    }
+
+    final class Behavior extends SwipeDismissBehavior<SnackbarLayout> {
+        @Override
+        public boolean onInterceptTouchEvent(CoordinatorLayout parent, SnackbarLayout child,
+                MotionEvent event) {
+            // We want to make sure that we disable any Snackbar timeouts if the user is
+            // currently touching the Snackbar. We restore the timeout when complete
+            if (parent.isPointInChildBounds(child, (int) event.getX(), (int) event.getY())) {
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_DOWN:
+                        SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
+                        break;
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
+                        break;
+                }
+            }
+
+            return super.onInterceptTouchEvent(parent, child, event);
+        }
+    }
+}
diff --git a/design/src/android/support/design/widget/SnackbarManager.java b/design/src/android/support/design/widget/SnackbarManager.java
new file mode 100644
index 0000000..3b09f17
--- /dev/null
+++ b/design/src/android/support/design/widget/SnackbarManager.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Manages {@link Snackbar}s.
+ */
+class SnackbarManager {
+
+    private static final int MSG_TIMEOUT = 0;
+
+    private static final int SHORT_DURATION_MS = 1500;
+    private static final int LONG_DURATION_MS = 2750;
+
+    private static SnackbarManager sSnackbarManager;
+
+    static SnackbarManager getInstance() {
+        if (sSnackbarManager == null) {
+            sSnackbarManager = new SnackbarManager();
+        }
+        return sSnackbarManager;
+    }
+
+    private final Object mLock;
+    private final Handler mHandler;
+
+    private SnackbarRecord mCurrentSnackbar;
+    private SnackbarRecord mNextSnackbar;
+
+    private SnackbarManager() {
+        mLock = new Object();
+        mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_TIMEOUT:
+                        handleTimeout((SnackbarRecord) message.obj);
+                        return true;
+                }
+                return false;
+            }
+        });
+    }
+
+    interface Callback {
+        void show();
+        void dismiss();
+    }
+
+    public void show(int duration, Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                // Means that the callback is already in the queue. We'll just update the duration
+                mCurrentSnackbar.duration = duration;
+
+                // If this is the Snackbar currently being shown, call re-schedule it's
+                // timeout
+                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
+                scheduleTimeoutLocked(mCurrentSnackbar);
+                return;
+            } else if (isNextSnackbar(callback)) {
+                // We'll just update the duration
+                mNextSnackbar.duration = duration;
+            } else {
+                // Else, we need to create a new record and queue it
+                mNextSnackbar = new SnackbarRecord(duration, callback);
+            }
+
+            if (mCurrentSnackbar != null) {
+                // If the new Snackbar isn't in position 0, we'll cancel the current one and wait
+                // in line
+                cancelSnackbarLocked(mCurrentSnackbar);
+            } else {
+                // Otherwise, just show it now
+                showNextSnackbarLocked();
+            }
+        }
+    }
+
+    public void dismiss(Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                cancelSnackbarLocked(mCurrentSnackbar);
+            }
+            if (isNextSnackbar(callback)) {
+                cancelSnackbarLocked(mNextSnackbar);
+            }
+        }
+    }
+
+    /**
+     * Should be called when a Snackbar is no longer displayed. This is after any exit
+     * animation has finished.
+     */
+    public void onDismissed(Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                // If the callback is from a Snackbar currently show, remove it and show a new one
+                mCurrentSnackbar = null;
+                if (mNextSnackbar != null) {
+                    showNextSnackbarLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Should be called when a Snackbar is being shown. This is after any entrance animation has
+     * finished.
+     */
+    public void onShown(Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                scheduleTimeoutLocked(mCurrentSnackbar);
+            }
+        }
+    }
+
+    public void cancelTimeout(Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
+            }
+        }
+    }
+
+    public void restoreTimeout(Callback callback) {
+        synchronized (mLock) {
+            if (isCurrentSnackbar(callback)) {
+                scheduleTimeoutLocked(mCurrentSnackbar);
+            }
+        }
+    }
+
+    private static class SnackbarRecord {
+        private Callback callback;
+        private int duration;
+
+        SnackbarRecord(int duration, Callback callback) {
+            this.callback = callback;
+            this.duration = duration;
+        }
+    }
+
+    private void showNextSnackbarLocked() {
+        if (mNextSnackbar != null) {
+            mCurrentSnackbar = mNextSnackbar;
+            mCurrentSnackbar.callback.show();
+            mNextSnackbar = null;
+        }
+    }
+
+    private void cancelSnackbarLocked(SnackbarRecord record) {
+        record.callback.dismiss();
+    }
+
+    private boolean isCurrentSnackbar(Callback callback) {
+        return mCurrentSnackbar != null && mCurrentSnackbar.callback == callback;
+    }
+
+    private boolean isNextSnackbar(Callback callback) {
+        return mNextSnackbar != null && mNextSnackbar.callback == callback;
+    }
+
+    private void scheduleTimeoutLocked(SnackbarRecord r) {
+        mHandler.removeCallbacksAndMessages(r);
+        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r),
+                r.duration == Snackbar.LENGTH_LONG
+                        ? LONG_DURATION_MS
+                        : SHORT_DURATION_MS);
+    }
+
+    private void handleTimeout(SnackbarRecord record) {
+        synchronized (mLock) {
+            if (mCurrentSnackbar == record || mNextSnackbar == record) {
+                cancelSnackbarLocked(record);
+            }
+        }
+    }
+
+}
diff --git a/design/src/android/support/design/widget/SwipeDismissBehavior.java b/design/src/android/support/design/widget/SwipeDismissBehavior.java
new file mode 100644
index 0000000..d6c1338
--- /dev/null
+++ b/design/src/android/support/design/widget/SwipeDismissBehavior.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.annotation.IntDef;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewDragHelper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interaction behavior plugin for child views of {@link CoordinatorLayout} to provide support
+ * for the 'swipe-to-dismiss' gesture.
+ */
+public class SwipeDismissBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
+
+    /**
+     * A view is not currently being dragged or animating as a result of a fling/snap.
+     */
+    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
+
+    /**
+     * A view is currently being dragged. The position is currently changing as a result
+     * of user input or simulated user input.
+     */
+    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
+
+    /**
+     * A view is currently settling into place as a result of a fling or
+     * predefined non-interactive motion.
+     */
+    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
+
+    /** @hide */
+    @IntDef({SWIPE_DIRECTION_START_TO_END, SWIPE_DIRECTION_END_TO_START, SWIPE_DIRECTION_ANY})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface SwipeDirection {}
+
+    /**
+     * Swipe direction that only allows swiping in the direction of start-to-end. That is
+     * left-to-right in LTR, or right-to-left in RTL.
+     */
+    public static final int SWIPE_DIRECTION_START_TO_END = 0;
+
+    /**
+     * Swipe direction that only allows swiping in the direction of end-to-start. That is
+     * right-to-left in LTR or left-to-right in RTL.
+     */
+    public static final int SWIPE_DIRECTION_END_TO_START = 1;
+
+    /**
+     * Swipe direction which allows swiping in either direction.
+     */
+    public static final int SWIPE_DIRECTION_ANY = 2;
+
+    private static final float DEFAULT_DRAG_DISMISS_THRESHOLD = 0.5f;
+    private static final float DEFAULT_ALPHA_START_DISTANCE = 0f;
+    private static final float DEFAULT_ALPHA_END_DISTANCE = DEFAULT_DRAG_DISMISS_THRESHOLD;
+
+    private ViewDragHelper mViewDragHelper;
+    private OnDismissListener mListener;
+    private boolean mIgnoreEvents;
+
+    private float mSensitivity = 0f;
+    private boolean mSensitivitySet;
+
+    private int mSwipeDirection = SWIPE_DIRECTION_ANY;
+    private float mDragDismissThreshold = DEFAULT_DRAG_DISMISS_THRESHOLD;
+    private float mAlphaStartSwipeDistance = DEFAULT_ALPHA_START_DISTANCE;
+    private float mAlphaEndSwipeDistance = DEFAULT_ALPHA_END_DISTANCE;
+
+    /**
+     * Callback interface used to notify the application that the view has been dismissed.
+     */
+    public interface OnDismissListener {
+        /**
+         * Called when {@code view} has been dismissed via swiping.
+         */
+        public void onDismiss(View view);
+
+        /**
+         * Called when the drag state has changed.
+         *
+         * @param state the new state. One of
+         * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
+         */
+        public void onDragStateChanged(int state);
+    }
+
+    /**
+     * Set the listener to be used when a dismiss event occurs.
+     *
+     * @param listener the listener to use.
+     */
+    public void setListener(OnDismissListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Sets the swipe direction for this behavior.
+     *
+     * @param direction one of the {@link #SWIPE_DIRECTION_START_TO_END},
+     *                  {@link #SWIPE_DIRECTION_END_TO_START} or {@link #SWIPE_DIRECTION_ANY}
+     */
+    public void setSwipeDirection(@SwipeDirection int direction) {
+        mSwipeDirection = direction;
+    }
+
+    /**
+     * Set the threshold for telling if a view has been dragged enough to be dismissed.
+     *
+     * @param distance a ratio of a view's width, values are clamped to 0 >= x <= 1f;
+     */
+    public void setDragDismissDistance(float distance) {
+        mDragDismissThreshold = clamp(0f, distance, 1f);
+    }
+
+    /**
+     * The minimum swipe distance before the view's alpha is modified.
+     *
+     * @param fraction the distance as a fraction of the view's width.
+     */
+    public void setStartAlphaSwipeDistance(float fraction) {
+        mAlphaStartSwipeDistance = clamp(0f, fraction, 1f);
+    }
+
+    /**
+     * The maximum swipe distance for the view's alpha is modified.
+     *
+     * @param fraction the distance as a fraction of the view's width.
+     */
+    public void setEndAlphaSwipeDistance(float fraction) {
+        mAlphaEndSwipeDistance = clamp(0f, fraction, 1f);
+    }
+
+    /**
+     * Set the sensitivity used for detecting the start of a swipe. This only takes effect if
+     * no touch handling has occured yet.
+     *
+     * @param sensitivity Multiplier for how sensitive we should be about detecting
+     *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
+     */
+    public void setSensitivity(float sensitivity) {
+        mSensitivity = sensitivity;
+        mSensitivitySet = true;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mIgnoreEvents = !parent.isPointInChildBounds(child,
+                        (int) event.getX(), (int) event.getY());
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                // Reset the ignore flag
+                if (mIgnoreEvents) {
+                    mIgnoreEvents = false;
+                    return false;
+                }
+                break;
+        }
+
+        if (mIgnoreEvents) {
+            return false;
+        }
+
+        ensureViewDragHelper(parent);
+        return mViewDragHelper.shouldInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+        if (mViewDragHelper != null) {
+            mViewDragHelper.processTouchEvent(event);
+            return true;
+        }
+        return false;
+    }
+
+    private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
+        private int mOriginalCapturedViewLeft;
+
+        @Override
+        public boolean tryCaptureView(View child, int pointerId) {
+            mOriginalCapturedViewLeft = child.getLeft();
+            return true;
+        }
+
+        @Override
+        public void onViewDragStateChanged(int state) {
+            if (mListener != null) {
+                mListener.onDragStateChanged(state);
+            }
+        }
+
+        @Override
+        public void onViewReleased(View child, float xvel, float yvel) {
+            final int childWidth = child.getWidth();
+            int targetLeft;
+            boolean dismiss = false;
+
+            if (shouldDismiss(child, xvel)) {
+                targetLeft = child.getLeft() < mOriginalCapturedViewLeft
+                        ? mOriginalCapturedViewLeft - childWidth
+                        : mOriginalCapturedViewLeft + childWidth;
+                dismiss = true;
+            } else {
+                // Else, reset back to the original left
+                targetLeft = mOriginalCapturedViewLeft;
+            }
+
+            if (mViewDragHelper.settleCapturedViewAt(targetLeft, child.getTop())) {
+                ViewCompat.postOnAnimation(child, new SettleRunnable(child, dismiss));
+            } else if (dismiss && mListener != null) {
+                mListener.onDismiss(child);
+            }
+        }
+
+        private boolean shouldDismiss(View child, float xvel) {
+            if (xvel != 0f) {
+                final boolean isRtl = ViewCompat.getLayoutDirection(child)
+                        == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+                if (mSwipeDirection == SWIPE_DIRECTION_ANY) {
+                    // We don't care about the direction so return true
+                    return true;
+                } else if (mSwipeDirection == SWIPE_DIRECTION_START_TO_END) {
+                    // We only allow start-to-end swiping, so the fling needs to be in the
+                    // correct direction
+                    return isRtl ? xvel < 0f : xvel > 0f;
+                } else if (mSwipeDirection == SWIPE_DIRECTION_END_TO_START) {
+                    // We only allow end-to-start swiping, so the fling needs to be in the
+                    // correct direction
+                    return isRtl ? xvel > 0f : xvel < 0f;
+                }
+            } else {
+                final int distance = child.getLeft() - mOriginalCapturedViewLeft;
+                final int thresholdDistance = Math.round(child.getWidth() * mDragDismissThreshold);
+                return Math.abs(distance) >= thresholdDistance;
+            }
+
+            return false;
+        }
+
+        @Override
+        public int getViewHorizontalDragRange(View child) {
+            return child.getWidth();
+        }
+
+        @Override
+        public int clampViewPositionHorizontal(View child, int left, int dx) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(child)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            int min, max;
+
+            if (mSwipeDirection == SWIPE_DIRECTION_START_TO_END) {
+                if (isRtl) {
+                    min = mOriginalCapturedViewLeft - child.getWidth();
+                    max = mOriginalCapturedViewLeft;
+                } else {
+                    min = mOriginalCapturedViewLeft;
+                    max = mOriginalCapturedViewLeft + child.getWidth();
+                }
+            } else if (mSwipeDirection == SWIPE_DIRECTION_END_TO_START) {
+                if (isRtl) {
+                    min = mOriginalCapturedViewLeft;
+                    max = mOriginalCapturedViewLeft + child.getWidth();
+                } else {
+                    min = mOriginalCapturedViewLeft - child.getWidth();
+                    max = mOriginalCapturedViewLeft;
+                }
+            } else {
+                min = mOriginalCapturedViewLeft - child.getWidth();
+                max = mOriginalCapturedViewLeft + child.getWidth();
+            }
+
+            return clamp(min, left, max);
+        }
+
+        @Override
+        public int clampViewPositionVertical(View child, int top, int dy) {
+            return child.getTop();
+        }
+
+        @Override
+        public void onViewPositionChanged(View child, int left, int top, int dx, int dy) {
+            final float startAlphaDistance = child.getWidth() * mAlphaStartSwipeDistance;
+            final float endAlphaDistance = child.getWidth() * mAlphaEndSwipeDistance;
+
+            if (left <= startAlphaDistance) {
+                ViewCompat.setAlpha(child, 1f);
+            } else if (left >= endAlphaDistance) {
+                ViewCompat.setAlpha(child, 0f);
+            } else {
+                // We're between the start and end distances
+                final float distance = fraction(startAlphaDistance, endAlphaDistance, left);
+                ViewCompat.setAlpha(child, clamp(0f, 1f - distance, 1f));
+            }
+        }
+    };
+
+    private void ensureViewDragHelper(ViewGroup parent) {
+        if (mViewDragHelper == null) {
+            mViewDragHelper = mSensitivitySet
+                    ? ViewDragHelper.create(parent, mSensitivity, mDragCallback)
+                    : ViewDragHelper.create(parent, mDragCallback);
+        }
+    }
+
+    private class SettleRunnable implements Runnable {
+        private final View mView;
+        private final boolean mDismiss;
+
+        SettleRunnable(View view, boolean dismiss) {
+            mView = view;
+            mDismiss = dismiss;
+        }
+
+        @Override
+        public void run() {
+            if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
+                ViewCompat.postOnAnimation(mView, this);
+            } else {
+                if (mDismiss && mListener != null) {
+                    mListener.onDismiss(mView);
+                }
+            }
+        }
+    }
+
+    private static float clamp(float min, float value, float max) {
+        return Math.min(Math.max(min, value), max);
+    }
+
+    private static int clamp(int min, int value, int max) {
+        return Math.min(Math.max(min, value), max);
+    }
+
+    /**
+     * Retrieve the current drag state of this behavior. This will return one of
+     * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
+     *
+     * @return The current drag state
+     */
+    public int getDragState() {
+        return mViewDragHelper != null ? mViewDragHelper.getViewDragState() : STATE_IDLE;
+    }
+
+    /**
+     * Linear interpolation between {@code startValue} and {@code endValue} by the fraction {@code
+     * fraction}.
+     */
+    static float lerp(float startValue, float endValue, float fraction) {
+        return startValue + (fraction * (endValue - startValue));
+    }
+
+    /**
+     * The fraction that {@code value} is between {@code startValue} and {@code endValue}.
+     */
+    static float fraction(float startValue, float endValue, float value) {
+        return (value - startValue) / (endValue - startValue);
+    }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 62f7056..d9df727 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -30,7 +30,6 @@
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.ViewPager;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
 import android.support.v7.app.ActionBar;
 import android.support.v7.internal.widget.TintManager;
 import android.support.v7.widget.AppCompatTextView;
@@ -44,7 +43,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Animation;
-import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
@@ -57,6 +55,8 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 
+import static android.support.design.widget.AnimationUtils.lerp;
+
 /**
  * TabLayout provides a horizontal layout to display tabs. <p> Population of the tabs to display is
  * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can
@@ -87,7 +87,6 @@
  */
 public class TabLayout extends HorizontalScrollView {
 
-    private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
     private static final int MAX_TAB_TEXT_LINES = 2;
 
     private static final int DEFAULT_HEIGHT = 48; // dps
@@ -270,8 +269,12 @@
      * part of a scrolling container such as {@link ViewPager}.
      * <p>
      * Calling this method does not update the selected tab, it is only used for drawing purposes.
+     *
+     * @param position current scroll position
+     * @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
+     * @param updateSelectedText Whether to update the text's selected state.
      */
-    public void setScrollPosition(int position, float positionOffset) {
+    public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
         if (isAnimationRunning(getAnimation())) {
             return;
         }
@@ -284,7 +287,9 @@
         scrollTo(calculateScrollXForTab(position, positionOffset), 0);
 
         // Update the 'selected state' view as we scroll
-        setSelectedTabView(Math.round(position + positionOffset));
+        if (updateSelectedText) {
+            setSelectedTabView(Math.round(position + positionOffset));
+        }
     }
 
     /**
@@ -308,11 +313,21 @@
      * through to all of the methods.
      */
     public ViewPager.OnPageChangeListener createOnPageChangeListener() {
-        return new ViewPager.SimpleOnPageChangeListener() {
+        return new ViewPager.OnPageChangeListener() {
+            private int mScrollState;
+
+            @Override
+            public void onPageScrollStateChanged(int state) {
+                mScrollState = state;
+            }
+
             @Override
             public void onPageScrolled(int position, float positionOffset,
                     int positionOffsetPixels) {
-                setScrollPosition(position, positionOffset);
+                // Update the scroll position, only update the text selection if we're being
+                // dragged
+                setScrollPosition(position, positionOffset,
+                        mScrollState == ViewPager.SCROLL_STATE_DRAGGING);
             }
 
             @Override
@@ -676,7 +691,7 @@
         if (getWindowToken() == null || !ViewCompat.isLaidOut(this)) {
             // If we don't have a window token, or we haven't been laid out yet just draw the new
             // position now
-            setScrollPosition(newPosition, 0f);
+            setScrollPosition(newPosition, 0f, true);
             return;
         }
 
@@ -688,11 +703,12 @@
             final Animation animation = new Animation() {
                 @Override
                 protected void applyTransformation(float interpolatedTime, Transformation t) {
-                    final float value = lerp(startScrollX, targetScrollX, interpolatedTime);
+                    final float value = AnimationUtils.lerp(startScrollX, targetScrollX,
+                            interpolatedTime);
                     scrollTo((int) value, 0);
                 }
             };
-            animation.setInterpolator(INTERPOLATOR);
+            animation.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
             animation.setDuration(duration);
             startAnimation(animation);
         }
@@ -705,8 +721,7 @@
         final int tabCount = mTabStrip.getChildCount();
         for (int i = 0; i < tabCount; i++) {
             final View child = mTabStrip.getChildAt(i);
-            final boolean isSelected = i == position;
-            child.setSelected(isSelected);
+            child.setSelected(i == position);
         }
     }
 
@@ -729,7 +744,7 @@
             if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
                     && newPosition != Tab.INVALID_POSITION) {
                 // If we don't currently have a tab, just draw the indicator
-                setScrollPosition(newPosition, 0f);
+                setScrollPosition(newPosition, 0f, true);
             } else {
                 animateToTab(newPosition);
             }
@@ -1349,11 +1364,11 @@
                     @Override
                     protected void applyTransformation(float interpolatedTime, Transformation t) {
                         setIndicatorPosition(
-                                (int) lerp(startLeft, targetLeft, interpolatedTime),
-                                (int) lerp(startRight, targetRight, interpolatedTime));
+                                (int) AnimationUtils.lerp(startLeft, targetLeft, interpolatedTime),
+                                (int) AnimationUtils.lerp(startRight, targetRight, interpolatedTime));
                     }
                 };
-                anim.setInterpolator(INTERPOLATOR);
+                anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                 anim.setDuration(duration);
                 anim.setAnimationListener(new Animation.AnimationListener() {
                     @Override
@@ -1385,12 +1400,4 @@
         }
     }
 
-    /**
-     * Linear interpolation between {@code startValue} and {@code endValue} by the fraction {@code
-     * fraction}.
-     */
-    static float lerp(float startValue, float endValue, float fraction) {
-        return startValue + (fraction * (endValue - startValue));
-    }
-
 }
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
new file mode 100644
index 0000000..4992d7f
--- /dev/null
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.os.Message;
+import android.support.design.R;
+import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Layout which wraps an {@link android.widget.EditText} to show a floating label when
+ * the hint is hidden due to the user inputting text.
+ */
+public class TextInputLayout extends LinearLayout {
+
+    private static final long ANIMATION_DURATION = 200;
+    private static final int MSG_UPDATE_LABEL = 0;
+
+    private EditText mEditText;
+    private CharSequence mHint;
+
+    private boolean mErrorEnabled;
+    private TextView mErrorView;
+    private int mErrorTextAppearance;
+
+    private ColorStateList mLabelTextColor;
+
+    private final CollapsingTextHelper mCollapsingTextHelper;
+    private final Handler mHandler;
+
+    public TextInputLayout(Context context) {
+        this(context, null);
+    }
+
+    public TextInputLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        setOrientation(VERTICAL);
+        setWillNotDraw(false);
+
+        mCollapsingTextHelper = new CollapsingTextHelper(this);
+        mHandler = new Handler(new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_UPDATE_LABEL:
+                        updateLabelVisibility(true);
+                        return true;
+                }
+                return false;
+            }
+        });
+
+        mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
+        mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator());
+        mCollapsingTextHelper.setCollapsedTextVerticalGravity(Gravity.TOP);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
+        mHint = a.getText(R.styleable.TextInputLayout_android_hint);
+
+        final int hintAppearance = a.getResourceId(
+                R.styleable.TextInputLayout_hintTextAppearance, -1);
+        if (hintAppearance != -1) {
+            mCollapsingTextHelper.setCollapsedTextAppearance(hintAppearance);
+        }
+
+        mErrorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0);
+        final boolean errorEnabled = a.getBoolean(R.styleable.TextInputLayout_errorEnabled, false);
+
+        // We create a ColorStateList using the specified text color, combining it with our
+        // theme's textColorHint
+        mLabelTextColor = createLabelTextColorStateList(
+                mCollapsingTextHelper.getCollapsedTextColor());
+
+        mCollapsingTextHelper.setCollapsedTextColor(mLabelTextColor.getDefaultColor());
+        mCollapsingTextHelper.setExpandedTextColor(mLabelTextColor.getDefaultColor());
+
+        a.recycle();
+
+        if (errorEnabled) {
+            setErrorEnabled(true);
+        }
+
+        if (ViewCompat.getImportantForAccessibility(this)
+                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            // Make sure we're important for accessibility if we haven't been explicitly not
+            ViewCompat.setImportantForAccessibility(this,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+
+        ViewCompat.setAccessibilityDelegate(this, new TextInputAccessibilityDelegate());
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (child instanceof EditText) {
+            params = setEditText((EditText) child, params);
+            super.addView(child, 0, params);
+        } else {
+            // Carry on adding the View...
+            super.addView(child, index, params);
+        }
+    }
+
+    private LayoutParams setEditText(EditText editText, ViewGroup.LayoutParams lp) {
+        // If we already have an EditText, throw an exception
+        if (mEditText != null) {
+            throw new IllegalArgumentException("We already have an EditText, can only have one");
+        }
+        mEditText = editText;
+
+        // Use the EditText's text size for our expanded text
+        mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize());
+
+        // Add a TextWatcher so that we know when the text input has changed
+        mEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void afterTextChanged(Editable s) {
+                mHandler.sendEmptyMessage(MSG_UPDATE_LABEL);
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+            }
+        });
+
+        // Add focus listener to the EditText so that we can notify the label that it is activated.
+        // Allows the use of a ColorStateList for the text color on the label
+        mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean focused) {
+                mHandler.sendEmptyMessage(MSG_UPDATE_LABEL);
+            }
+        });
+
+        // If we do not have a valid hint, try and retrieve it from the EditText
+        if (TextUtils.isEmpty(mHint)) {
+            setHint(mEditText.getHint());
+            // Clear the EditText's hint as we will display it ourselves
+            mEditText.setHint(null);
+        }
+
+        if (mErrorView != null) {
+            // Add some start/end padding to the error so that it matches the EditText
+            ViewCompat.setPaddingRelative(mErrorView, ViewCompat.getPaddingStart(mEditText),
+                    0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom());
+        }
+
+        // Update the label visibility with no animation
+        updateLabelVisibility(false);
+
+        // Create a new FrameLayout.LayoutParams so that we can add enough top margin
+        // to the EditText so make room for the label
+        LayoutParams newLp = new LayoutParams(lp);
+        Paint paint = new Paint();
+        paint.setTextSize(mCollapsingTextHelper.getExpandedTextSize());
+        newLp.topMargin = (int) -paint.ascent();
+
+        return newLp;
+    }
+
+    private void updateLabelVisibility(boolean animate) {
+        boolean hasText = !TextUtils.isEmpty(mEditText.getText());
+        boolean isFocused = mEditText.isFocused();
+
+        mCollapsingTextHelper.setCollapsedTextColor(mLabelTextColor.getColorForState(
+                isFocused ? FOCUSED_STATE_SET : EMPTY_STATE_SET,
+                mLabelTextColor.getDefaultColor()));
+
+        if (hasText || isFocused) {
+            // We should be showing the label so do so if it isn't already
+            collapseHint(animate);
+        } else {
+            // We should not be showing the label so hide it
+            expandHint(animate);
+        }
+    }
+
+    /**
+     * @return the {@link android.widget.EditText} text input
+     */
+    public EditText getEditText() {
+        return mEditText;
+    }
+
+    /**
+     * Set the hint to be displayed in the floating label
+     */
+    public void setHint(CharSequence hint) {
+        mHint = hint;
+        mCollapsingTextHelper.setText(hint);
+
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+    }
+
+    /**
+     * Whether the error functionality is enabled or not in this layout. Enabling this
+     * functionality before setting an error message via {@link #setError(CharSequence)}, will mean
+     * that this layout will not change size when an error is displayed.
+     *
+     * @attr R.attr.errorEnabled
+     */
+    public void setErrorEnabled(boolean enabled) {
+        if (mErrorEnabled != enabled) {
+            if (enabled) {
+                mErrorView = new TextView(getContext());
+                mErrorView.setTextAppearance(getContext(), mErrorTextAppearance);
+                mErrorView.setVisibility(INVISIBLE);
+                addView(mErrorView);
+
+                if (mEditText != null) {
+                    // Add some start/end padding to the error so that it matches the EditText
+                    ViewCompat.setPaddingRelative(mErrorView, ViewCompat.getPaddingStart(mEditText),
+                            0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom());
+                }
+            } else {
+                removeView(mErrorView);
+                mErrorView = null;
+            }
+            mErrorEnabled = enabled;
+        }
+    }
+
+    /**
+     * Sets an error message that will be displayed below our {@link EditText}. If the
+     * {@code error} is {@code null}, the error message will be cleared.
+     * <p>
+     * If the error functionality has not been enabled via {@link #setErrorEnabled(boolean)}, then
+     * it will be automatically enabled if {@code error} is not empty.
+     *
+     * @param error Error message to display, or null to clear
+     */
+    public void setError(CharSequence error) {
+        if (!mErrorEnabled) {
+            if (TextUtils.isEmpty(error)) {
+                // If error isn't enabled, and the error is empty, just return
+                return;
+            }
+            // Else, we'll assume that they want to enable the error functionality
+            setErrorEnabled(true);
+        }
+
+        if (!TextUtils.isEmpty(error)) {
+            mErrorView.setText(error);
+            mErrorView.setVisibility(VISIBLE);
+            ViewCompat.setAlpha(mErrorView, 0f);
+            ViewCompat.animate(mErrorView)
+                    .alpha(1f)
+                    .setDuration(ANIMATION_DURATION)
+                    .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+                    .setListener(null)
+                    .start();
+        } else {
+            if (mErrorView.getVisibility() == VISIBLE) {
+                ViewCompat.animate(mErrorView)
+                        .alpha(0f)
+                        .setDuration(ANIMATION_DURATION)
+                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+                        .setListener(new ViewPropertyAnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(View view) {
+                                mErrorView.setText(null);
+                                mErrorView.setVisibility(INVISIBLE);
+                            }
+                        }).start();
+            }
+        }
+
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        mCollapsingTextHelper.draw(canvas);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        mCollapsingTextHelper.onLayout(changed, left, top, right, bottom);
+
+        if (mEditText != null) {
+            final int l = mEditText.getLeft() + mEditText.getPaddingLeft();
+            final int r = mEditText.getRight() - mEditText.getPaddingRight();
+
+            mCollapsingTextHelper.setExpandedBounds(l,
+                    mEditText.getTop() + mEditText.getPaddingTop(),
+                    r, mEditText.getBottom() - mEditText.getPaddingBottom());
+
+            // Set the collapsed bounds to be the the full height (minus padding) to match the
+            // EditText's editable area
+            mCollapsingTextHelper.setCollapsedBounds(l, getPaddingTop(),
+                    r, bottom - top - getPaddingBottom());
+        }
+    }
+
+    private void collapseHint(boolean animate) {
+        if (animate) {
+            animateToExpansionFraction(1f);
+        } else {
+            mCollapsingTextHelper.setExpansionFraction(1f);
+        }
+    }
+
+    private void expandHint(boolean animate) {
+        if (animate) {
+            animateToExpansionFraction(0f);
+        } else {
+            mCollapsingTextHelper.setExpansionFraction(0f);
+        }
+    }
+
+    private void animateToExpansionFraction(final float target) {
+        final float current = mCollapsingTextHelper.getExpansionFraction();
+
+        Animation anim = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                mCollapsingTextHelper.setExpansionFraction(
+                        AnimationUtils.lerp(current, target, interpolatedTime));
+            }
+        };
+        anim.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
+        anim.setDuration(ANIMATION_DURATION);
+        startAnimation(anim);
+    }
+
+    private ColorStateList createLabelTextColorStateList(int color) {
+        final int[][] states = new int[2][];
+        final int[] colors = new int[2];
+        int i = 0;
+
+        // Focused
+        states[i] = FOCUSED_STATE_SET;
+        colors[i] = color;
+        i++;
+
+        states[i] = EMPTY_STATE_SET;
+        colors[i] = getThemeAttrColor(android.R.attr.textColorHint);
+        i++;
+
+        return new ColorStateList(states, colors);
+    }
+
+    private int getThemeAttrColor(int attr) {
+        TypedValue tv = new TypedValue();
+        if (getContext().getTheme().resolveAttribute(attr, tv, true)) {
+            return tv.data;
+        } else {
+            return Color.MAGENTA;
+        }
+    }
+
+    private class TextInputAccessibilityDelegate extends AccessibilityDelegateCompat {
+        @Override
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(host, event);
+            event.setClassName(TextInputLayout.class.getSimpleName());
+        }
+
+        @Override
+        public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+            super.onPopulateAccessibilityEvent(host, event);
+
+            final CharSequence text = mCollapsingTextHelper.getText();
+            if (!TextUtils.isEmpty(text)) {
+                event.getText().add(text);
+            }
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            info.setClassName(TextInputLayout.class.getSimpleName());
+
+            final CharSequence text = mCollapsingTextHelper.getText();
+            if (!TextUtils.isEmpty(text)) {
+                info.setText(text);
+            }
+            if (mEditText != null) {
+                info.setLabelFor(mEditText);
+            }
+            final CharSequence error = mErrorView != null ? mErrorView.getText() : null;
+            if (!TextUtils.isEmpty(error)) {
+                info.setContentInvalid(true);
+                info.setError(error);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index 1574cb7..baf8719 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -107,6 +107,9 @@
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_TAGS := optional
 
+res.COMMON := $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),android-support-v17-leanback-res,,COMMON)
+leanback.docs.src_files := $(leanback.docs.src_files) $(call all-java-files-under, ../../../../$(res.COMMON))
+
 intermediates.COMMON := $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),android-support-v17-leanback,,COMMON)
 
 LOCAL_SRC_FILES := $(leanback.docs.src_files)
@@ -162,6 +165,7 @@
 leanback.docs.src_files :=
 leanback.docs.java_libraries :=
 intermediates.COMMON :=
+res.COMMON :=
 leanback_internal_api_file :=
 leanback_stubs_stamp :=
 leanback.docs.stubpackages :=
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml
new file mode 100644
index 0000000..df3aca2
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="translationX"
+        android:valueFrom="@dimen/lb_guidedstep_slide_end_distance"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:valueType="floatType" />
+
+</set>
\ No newline at end of file
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml
new file mode 100644
index 0000000..49ddc12
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="translationX"
+        android:valueFrom="@dimen/lb_guidedstep_slide_start_distance"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml
new file mode 100644
index 0000000..d481273
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="translationX"
+        android:valueFrom="0.0"
+        android:valueTo="@dimen/lb_guidedstep_slide_end_distance"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="alpha"
+        android:valueFrom="1.0"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml
new file mode 100644
index 0000000..b172e86
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="translationX"
+        android:valueFrom="0.0"
+        android:valueTo="@dimen/lb_guidedstep_slide_start_distance"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:propertyName="alpha"
+        android:valueFrom="1.0"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator/lb_decelerator_2.xml b/v17/leanback/res/animator/lb_decelerator_2.xml
new file mode 100644
index 0000000..b1f886a
--- /dev/null
+++ b/v17/leanback/res/animator/lb_decelerator_2.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:factor="2.0"/>
diff --git a/v17/leanback/res/animator/lb_guidance_entry.xml b/v17/leanback/res/animator/lb_guidance_entry.xml
new file mode 100644
index 0000000..e10d2ef
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidance_entry.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+
+    <set android:ordering="together">
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_delay"
+            android:propertyName="translationX"
+            android:valueFrom="@dimen/lb_guidance_entry_translationX"
+            android:valueTo="@dimen/lb_guidance_entry_translationX"
+            android:valueType="floatType" />
+
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_delay"
+            android:propertyName="alpha"
+            android:valueFrom="0.0"
+            android:valueTo="0.0"
+            android:valueType="floatType" />
+    </set>
+
+    <set android:ordering="together">
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_duration"
+            android:interpolator="@android:interpolator/decelerate_quad"
+            android:propertyName="translationX"
+            android:valueFrom="@dimen/lb_guidance_entry_translationX"
+            android:valueTo="0.0"
+            android:valueType="floatType" />
+
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_duration"
+            android:interpolator="@android:interpolator/decelerate_quad"
+            android:propertyName="alpha"
+            android:valueFrom="0.0"
+            android:valueTo="1.0"
+            android:valueType="floatType" />
+    </set>
+
+</set>
diff --git a/v17/leanback/res/animator/lb_guidedactions_entry.xml b/v17/leanback/res/animator/lb_guidedactions_entry.xml
new file mode 100644
index 0000000..ec6c655
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_entry.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+
+    <set android:ordering="together">
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_delay"
+            android:propertyName="translationX"
+            android:valueFrom="@dimen/lb_guidedactions_entry_translationX"
+            android:valueTo="@dimen/lb_guidedactions_entry_translationX"
+            android:valueType="floatType" />
+
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_delay"
+            android:propertyName="alpha"
+            android:valueFrom="0.0"
+            android:valueTo="0.0"
+            android:valueType="floatType" />
+    </set>
+
+    <set android:ordering="together">
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_duration"
+            android:interpolator="@android:interpolator/decelerate_quad"
+            android:propertyName="translationX"
+            android:valueFrom="@dimen/lb_guidedactions_entry_translationX"
+            android:valueTo="0.0"
+            android:valueType="floatType" />
+
+        <objectAnimator
+            android:duration="@integer/lb_guidedstep_entry_animation_duration"
+            android:interpolator="@android:interpolator/decelerate_quad"
+            android:propertyName="alpha"
+            android:valueFrom="0.0"
+            android:valueTo="1.0"
+            android:valueType="floatType" />
+    </set>
+</set>
\ No newline at end of file
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_checked.xml b/v17/leanback/res/animator/lb_guidedactions_item_checked.xml
new file mode 100644
index 0000000..463b9f7
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_item_checked.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_guidedactions_item_animation_duration"
+    android:propertyName="alpha"
+    android:valueFrom="0.0"
+    android:valueTo="1.0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_pressed.xml b/v17/leanback/res/animator/lb_guidedactions_item_pressed.xml
new file mode 100644
index 0000000..d00e13b
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_item_pressed.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_guidedactions_item_animation_duration"
+    android:propertyName="alpha"
+    android:valueFrom="1.0"
+    android:valueTo="0.2"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_unchecked.xml b/v17/leanback/res/animator/lb_guidedactions_item_unchecked.xml
new file mode 100644
index 0000000..86525c8
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_item_unchecked.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_guidedactions_item_animation_duration"
+    android:propertyName="alpha"
+    android:valueFrom="1.0"
+    android:valueTo="0.0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_unpressed.xml b/v17/leanback/res/animator/lb_guidedactions_item_unpressed.xml
new file mode 100644
index 0000000..0cf30a4
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_item_unpressed.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_guidedactions_item_animation_duration"
+    android:propertyName="alpha"
+    android:valueFrom="0.2"
+    android:valueTo="1.0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml b/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml
new file mode 100644
index 0000000..f829eb3
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_guidedactions_animation_duration"
+    android:propertyName="alpha"
+    android:valueFrom="1.0"
+    android:valueTo="0.0"
+    android:interpolator="@animator/lb_decelerator_2"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_selector_show.xml b/v17/leanback/res/animator/lb_guidedactions_selector_show.xml
new file mode 100644
index 0000000..e8d69e5
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedactions_selector_show.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together">
+
+    <objectAnimator
+        android:duration="@integer/lb_guidedactions_animation_duration"
+        android:propertyName="alpha"
+        android:valueFrom="0"
+        android:valueTo="1.0"
+        android:interpolator="@animator/lb_decelerator_2"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@integer/lb_guidedactions_animation_duration"
+        android:propertyName="scaleY"
+        android:interpolator="@animator/lb_decelerator_2"
+        android:valueType="floatType" />
+</set>
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_in_from_end.xml b/v17/leanback/res/animator/lb_guidedstep_slide_in_from_end.xml
new file mode 100644
index 0000000..1dacdbc
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_in_from_end.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="translationX"
+        android:valueFrom="@dimen/lb_guidedstep_slide_end_distance"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_in_from_start.xml b/v17/leanback/res/animator/lb_guidedstep_slide_in_from_start.xml
new file mode 100644
index 0000000..3c01324
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_in_from_start.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="translationX"
+        android:valueFrom="@dimen/lb_guidedstep_slide_start_distance"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="alpha"
+        android:valueFrom="0.0"
+        android:valueTo="1.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_out_to_end.xml b/v17/leanback/res/animator/lb_guidedstep_slide_out_to_end.xml
new file mode 100644
index 0000000..879a0cf
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_out_to_end.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="translationX"
+        android:valueFrom="0.0"
+        android:valueTo="@dimen/lb_guidedstep_slide_end_distance"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="alpha"
+        android:valueFrom="1.0"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_out_to_start.xml b/v17/leanback/res/animator/lb_guidedstep_slide_out_to_start.xml
new file mode 100644
index 0000000..4c9af82
--- /dev/null
+++ b/v17/leanback/res/animator/lb_guidedstep_slide_out_to_start.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="together" >
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="translationX"
+        android:valueFrom="0.0"
+        android:valueTo="@dimen/lb_guidedstep_slide_start_distance"
+        android:valueType="floatType" />
+
+    <objectAnimator
+        android:duration="@android:integer/config_longAnimTime"
+        android:propertyName="alpha"
+        android:valueFrom="1.0"
+        android:valueTo="0.0"
+        android:valueType="floatType" />
+
+</set>
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png
new file mode 100644
index 0000000..f06c02d
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png
new file mode 100644
index 0000000..149e214
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
new file mode 100644
index 0000000..6a65ccf
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_guidedactions_item_checkmark.xml b/v17/leanback/res/drawable/lb_guidedactions_item_checkmark.xml
new file mode 100644
index 0000000..ec7903b
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_guidedactions_item_checkmark.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+
+    <size
+        android:height="@dimen/lb_guidedactions_item_checkmark_diameter"
+        android:width="@dimen/lb_guidedactions_item_checkmark_diameter" />
+
+    <solid android:color="@color/lb_tv_white" />
+
+</shape>
diff --git a/v17/leanback/res/layout/lb_guidance.xml b/v17/leanback/res/layout/lb_guidance.xml
new file mode 100644
index 0000000..28c0220
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidance.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <RelativeLayout
+        style="?attr/guidanceContainerStyle" >
+
+        <ImageView
+            android:id="@+id/guidance_icon"
+            style="?attr/guidanceIconStyle"
+            tools:ignore="ContentDescription" />
+
+        <TextView
+            android:id="@+id/guidance_title"
+            style="?attr/guidanceTitleStyle" />
+
+        <TextView
+            android:id="@+id/guidance_breadcrumb"
+            style="?attr/guidanceBreadcrumbStyle" />
+
+        <TextView
+            android:id="@+id/guidance_description"
+            style="?attr/guidanceDescriptionStyle" />
+
+    </RelativeLayout>
+
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_guidedactions.xml b/v17/leanback/res/layout/lb_guidedactions.xml
new file mode 100644
index 0000000..43617c9
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedactions.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Layout for the settings list fragment -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <RelativeLayout
+        style="?attr/guidedActionsContainerStyle" >
+
+        <FrameLayout
+            android:id="@+id/guidedactions_selector"
+            style="?attr/guidedActionsSelectorStyle" />
+
+        <android.support.v17.leanback.widget.VerticalGridView
+            android:id="@+id/guidedactions_list"
+            style="?attr/guidedActionsListStyle" />
+
+    </RelativeLayout>
+
+</RelativeLayout>
diff --git a/v17/leanback/res/layout/lb_guidedactions_item.xml b/v17/leanback/res/layout/lb_guidedactions_item.xml
new file mode 100644
index 0000000..dfda22f
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedactions_item.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Layout for an action item displayed in the 2 pane actions fragment. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    style="?attr/guidedActionItemContainerStyle" >
+
+    <ImageView
+        android:id="@+id/guidedactions_item_checkmark"
+        style="?attr/guidedActionItemCheckmarkStyle"
+        tools:ignore="ContentDescription" />
+
+    <ImageView
+        android:id="@+id/guidedactions_item_icon"
+        style="?attr/guidedActionItemIconStyle"
+        tools:ignore="ContentDescription" />
+
+    <LinearLayout
+        android:id="@+id/guidedactions_item_content"
+        style="?attr/guidedActionItemContentStyle" >
+
+        <TextView
+            android:id="@+id/guidedactions_item_title"
+            style="?attr/guidedActionItemTitleStyle" />
+
+        <TextView
+            android:id="@+id/guidedactions_item_description"
+            style="?attr/guidedActionItemDescriptionStyle" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/guidedactions_item_chevron"
+        style="?attr/guidedActionItemChevronStyle"
+        tools:ignore="ContentDescription" />
+
+</LinearLayout>
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
new file mode 100644
index 0000000..6e0b7ad
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Layout for the frame of a 2 pane actions fragment. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content_frame"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <FrameLayout
+        android:id="@+id/content_fragment"
+        android:layout_width="@dimen/lb_guidedstep_guidance_section_width"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true" />
+
+    <FrameLayout
+        android:id="@+id/action_fragment"
+        android:layout_width="@dimen/lb_guidedactions_section_width_with_shadow"
+        android:layout_height="match_parent"
+        android:layout_alignParentEnd="true" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/values-ldrtl/dimens.xml b/v17/leanback/res/values-ldrtl/dimens.xml
new file mode 100644
index 0000000..9f54273
--- /dev/null
+++ b/v17/leanback/res/values-ldrtl/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <!-- GuidedStepFragment -->
+    <dimen name="lb_guidedstep_slide_start_distance">200dp</dimen>
+    <dimen name="lb_guidedstep_slide_end_distance">-200dp</dimen>
+    <dimen name="lb_guidance_entry_translationX">120dp</dimen>
+    <dimen name="lb_guidedactions_entry_translationX">-384dp</dimen>
+    <!-- end GuidedStepFragment -->
+
+</resources>
diff --git a/v17/leanback/res/values-v19/themes.xml b/v17/leanback/res/values-v19/themes.xml
index a466ad7..53befec 100644
--- a/v17/leanback/res/values-v19/themes.xml
+++ b/v17/leanback/res/values-v19/themes.xml
@@ -20,5 +20,8 @@
         <item name="playbackProgressPrimaryColor">@color/lb_playback_progress_color_no_theme</item>
         <item name="playbackControlsIconHighlightColor">@color/lb_playback_icon_highlight_no_theme</item>
         <item name="defaultBrandColor">@color/lb_default_brand_color</item>
+
+        <item name="android:windowOverscan">true</item>
+        <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
     </style>
 </resources>
diff --git a/v17/leanback/res/values-v21/themes.xml b/v17/leanback/res/values-v21/themes.xml
index 9061674..3b48dae 100644
--- a/v17/leanback/res/values-v21/themes.xml
+++ b/v17/leanback/res/values-v21/themes.xml
@@ -21,5 +21,8 @@
         <item name="playbackControlsIconHighlightColor">?android:attr/colorAccent</item>
         <item name="defaultBrandColor">?android:attr/colorPrimary</item>
         <item name="android:colorPrimary">@color/lb_default_brand_color</item>
+
+        <item name="android:windowOverscan">true</item>
+        <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
     </style>
 </resources>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 923ba3e..1b77694 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -278,6 +278,156 @@
         <attr name="overlayDimActiveLevel" format="fraction" />
         <!-- Default level of dimming for dimmed views. -->
         <attr name="overlayDimDimmedLevel" format="fraction" />
+    </declare-styleable>
+
+    <declare-styleable name="LeanbackGuidedStepTheme">
+
+        <!-- Theme attribute for the overall theme used in a GuidedStepFragment. The Leanback themes
+             set the default for this, but a custom theme that does not derive from a leanback theme
+             can set this to <code>@style/Theme.Leanback.GuidedStep</code> in order to specify the
+             default GuidedStepFragment styles. -->
+        <attr name="guidedStepTheme" format="reference" />
+
+        <!-- @hide
+             Theme attribute used to inspect theme inheritance. -->
+        <attr name="guidedStepThemeFlag" format="boolean" />
+
+        <!-- Theme attribute for the animation used when a guided step element is animated in on
+             fragment stack push. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedstep_slide_in_from_end}. -->
+        <attr name="guidedStepEntryAnimation" format="reference" />
+        <!-- Theme attribute for the animation used when a guided step element is animated out on
+             fragment stack push. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedstep_slide_out_to_start}. -->
+        <attr name="guidedStepExitAnimation" format="reference" />
+        <!-- Theme attribute for the animation used when a guided step element is animated in on
+             fragment stack pop. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedstep_slide_in_from_start}. -->
+        <attr name="guidedStepReentryAnimation" format="reference" />
+        <!-- Theme attribute for the animation used when a guided step element is animated out on
+             fragment stack pop. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedstep_slide_out_to_end}. -->
+        <attr name="guidedStepReturnAnimation" format="reference" />
+
+        <!-- Theme attribute for the animation used when the guidance is animated in at activity
+             start. Default is {@link android.support.v17.leanback.R.animator#lb_guidance_entry}.
+             -->
+        <attr name="guidanceEntryAnimation" format="reference" />
+        <!-- Theme attribute for the style of the main container in a GuidanceStylist. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceContainerStyle}.-->
+        <attr name="guidanceContainerStyle" format="reference" />
+        <!-- Theme attribute for the style of the title in a GuidanceStylist. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceTitleStyle}. -->
+        <attr name="guidanceTitleStyle" format="reference" />
+        <!-- Theme attribute for the style of the description in a GuidanceStylist. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceDescriptionStyle}. -->
+        <attr name="guidanceDescriptionStyle" format="reference" />
+        <!-- Theme attribute for the style of the breadcrumb in a GuidanceStylist. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceBreadcrumbStyle}. -->
+        <attr name="guidanceBreadcrumbStyle" format="reference" />
+        <!-- Theme attribute for the style of the icon in a GuidanceStylist. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceIconStyle}. -->
+        <attr name="guidanceIconStyle" format="reference" />
+
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when the actions
+             list is animated in at activity start. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_entry}. -->
+        <attr name="guidedActionsEntryAnimation" format="reference" />
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when the action
+             selector is animated in at activity start. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_selector_show}. -->
+        <attr name="guidedActionsSelectorShowAnimation" format="reference" />
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when the action
+             selector is animated in at activity start. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_selector_hide}. -->
+        <attr name="guidedActionsSelectorHideAnimation" format="reference" />
+        <!-- Theme attribute for the style of the container in a GuidedActionsPresenter. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionsContainerStyle}. -->
+        <attr name="guidedActionsContainerStyle" format="reference" />
+        <!-- Theme attribute for the style of the item selector in a GuidedActionsPresenter. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionsSelectorStyle}. -->
+        <attr name="guidedActionsSelectorStyle" format="reference" />
+        <!-- Theme attribute for the style of the list in a GuidedActionsPresenter. Default is
+             {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionsListStyle}.-->
+        <attr name="guidedActionsListStyle" format="reference" />
+
+        <!-- Theme attribute for the style of the container of a single action in a
+             GuidedActionsPresenter. Default is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemContainerStyle}. -->
+        <attr name="guidedActionItemContainerStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's checkmark in a GuidedActionsPresenter.
+             Default is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemCheckmarkStyle}. -->
+        <attr name="guidedActionItemCheckmarkStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's icon in a GuidedActionsPresenter. Default
+             is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemIconStyle}. -->
+        <attr name="guidedActionItemIconStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's content in a GuidedActionsPresenter.
+             Default is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemContentStyle}. -->
+        <attr name="guidedActionItemContentStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's title in a GuidedActionsPresenter. Default
+             is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemTitleStyle}. -->
+        <attr name="guidedActionItemTitleStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's description in a GuidedActionsPresenter.
+             Default is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemDescriptionStyle}. -->
+        <attr name="guidedActionItemDescriptionStyle" format="reference" />
+        <!-- Theme attribute for the style of an action's chevron decoration in a
+             GuidedActionsPresenter. Default is {@link
+             android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemChevronStyle}. -->
+        <attr name="guidedActionItemChevronStyle" format="reference" />
+
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when an action
+             is checked. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_item_checked}. -->
+        <attr name="guidedActionCheckedAnimation" format="reference" />
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when an action
+             is unchecked. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_item_unchecked}. -->
+        <attr name="guidedActionUncheckedAnimation" format="reference" />
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when an action
+             is pressed. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_item_pressed}. -->
+        <attr name="guidedActionPressedAnimation" format="reference" />
+        <!-- Theme attribute for the animation used in a GuidedActionsPresenter when an action
+             is unpressed. Default is {@link
+             android.support.v17.leanback.R.animator#lb_guidedactions_item_unpressed}. -->
+        <attr name="guidedActionUnpressedAnimation" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the alpha value of the chevron
+             decoration when its action is enabled. Default is {@link
+             android.support.v17.leanback.R.string#lb_guidedactions_item_enabled_chevron_alpha}. -->
+        <attr name="guidedActionEnabledChevronAlpha" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the alpha value of the chevron
+             decoration when its action is disabled. Default is {@link
+             android.support.v17.leanback.R.string#lb_guidedactions_item_disabled_chevron_alpha}. -->
+        <attr name="guidedActionDisabledChevronAlpha" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the width of the text area of
+             a single action when there is an icon present. Default is {@link
+             android.support.v17.leanback.R.dimen#lb_guidedactions_item_text_width}. -->
+        <attr name="guidedActionContentWidth" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the width of the text area of
+             a single action when there is no icon present. Default is {@link
+             android.support.v17.leanback.R.dimen#lb_guidedactions_item_text_width_no_icon}. -->
+        <attr name="guidedActionContentWidthNoIcon" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the max lines of the title text
+             view when the action's isMultilineDescription is set to false. Default is {@link
+             android.support.v17.leanback.R.integer#lb_guidedactions_item_title_min_lines}. -->
+        <attr name="guidedActionTitleMinLines" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the max lines of the title text
+             view when the action's isMultilineDescription is set to true. Default is {@link
+             android.support.v17.leanback.R.integer#lb_guidedactions_item_title_max_lines}. -->
+        <attr name="guidedActionTitleMaxLines" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the max lines of the title text
+             view when the action's isMultilineDescription is set to false. Default is {@link
+             android.support.v17.leanback.R.integer#lb_guidedactions_item_description_min_lines}. -->
+        <attr name="guidedActionDescriptionMinLines" format="reference" />
+        <!-- Theme attribute used in a GuidedActionsPresenter for the vertical padding between
+             action views in the list. Default is {@link
+             android.support.v17.leanback.R.dimen#lb_guidedactions_vertical_padding}. -->
+        <attr name="guidedActionVerticalPadding" format="reference" />
 
     </declare-styleable>
 
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index ba65d2f..1549af9 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -65,4 +65,14 @@
     <color name="lb_playback_controls_time_text_color">#B2EEEEEE</color>
 
     <color name="lb_search_plate_hint_text_color">#FFCCCCCC</color>
+
+    <!-- GuidedStepFragment -->
+    <color name="lb_tv_white">#FFCCCCCC</color>
+
+    <!-- refactor naming here -->
+    <color name="lb_guidedactions_background">#FF111111</color>
+    <color name="lb_guidedactions_selector_color">#26FFFFFF</color>
+    <color name="lb_guidedactions_item_unselected_text_color">#FFF1F1F1</color>
+    <!-- end refactor naming -->
+
 </resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 8c4c198..c248af7 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -206,4 +206,45 @@
 
     <dimen name="lb_rounded_rect_corner_radius">2dp</dimen>
 
+    <!-- GuidedStepFragment -->
+    <dimen name="lb_guidedstep_guidance_section_width">576dp</dimen>
+    <dimen name="lb_guidedstep_slide_start_distance">-200dp</dimen>
+    <dimen name="lb_guidedstep_slide_end_distance">200dp</dimen>
+
+    <dimen name="lb_guidance_entry_translationX">-120dp</dimen>
+
+    <dimen name="lb_guidedactions_entry_translationX">384dp</dimen>
+    <dimen name="lb_guidedactions_section_width">384dp</dimen>
+    <dimen name="lb_guidedactions_section_width_with_shadow">400dp</dimen>
+    <dimen name="lb_guidedactions_elevation">12dp</dimen>
+    <dimen name="lb_guidedactions_selector_min_height">8dp</dimen>
+    <dimen name="lb_guidedactions_vertical_padding">12dp</dimen>
+
+    <item name="lb_guidedactions_item_unselected_text_alpha" format="float" type="string">1.00</item>
+    <item name="lb_guidedactions_item_unselected_description_text_alpha" format="float" type="string">0.50</item>
+    <item name="lb_guidedactions_item_enabled_chevron_alpha" format="float" type="string">1.00</item>
+    <item name="lb_guidedactions_item_disabled_chevron_alpha" format="float" type="string">0.50</item>
+
+    <dimen name="lb_guidedactions_item_text_width">248dp</dimen>
+    <dimen name="lb_guidedactions_item_text_width_no_icon">284dp</dimen>
+    <dimen name="lb_guidedactions_item_min_height">64dp</dimen>
+    <dimen name="lb_guidedactions_item_start_padding">20dp</dimen>
+    <dimen name="lb_guidedactions_item_end_padding">28dp</dimen>
+    <dimen name="lb_guidedactions_item_delimiter_padding">4dp</dimen>
+    <dimen name="lb_guidedactions_item_checkmark_diameter">8dp</dimen>
+    <dimen name="lb_guidedactions_item_icon_width">32dp</dimen>
+    <dimen name="lb_guidedactions_item_icon_height">32dp</dimen>
+    <dimen name="lb_guidedactions_item_title_font_size">18sp</dimen>
+    <dimen name="lb_guidedactions_item_description_font_size">12sp</dimen>
+
+    <integer name="lb_guidedstep_entry_animation_delay">550</integer>
+    <integer name="lb_guidedstep_entry_animation_duration">250</integer>
+
+    <integer name="lb_guidedactions_item_animation_duration">100</integer>
+    <integer name="lb_guidedactions_animation_duration">150</integer>
+    <integer name="lb_guidedactions_item_title_min_lines">1</integer>
+    <integer name="lb_guidedactions_item_title_max_lines">3</integer>
+    <integer name="lb_guidedactions_item_description_min_lines">2</integer>
+    <!-- end GuidedStepFragment -->
+
 </resources>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 71c5122..3ee2821 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -290,4 +290,175 @@
         <item name="closed_captioning">@drawable/lb_ic_cc</item>
     </style>
 
+    <!-- Style for the main container view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceContainerStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:paddingStart">48dp</item>
+        <item name="android:paddingEnd">16dp</item>
+        <item name="android:clipToPadding">false</item>
+    </style>
+
+    <!-- Style for the title view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceTitleStyle">
+        <item name="android:layout_toStartOf">@id/guidance_icon</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignWithParentIfMissing">true</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:gravity">end</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:paddingBottom">4dp</item>
+        <item name="android:paddingTop">2dp</item>
+        <item name="android:textColor">#FFF1F1F1</item>
+        <item name="android:textSize">36sp</item>
+    </style>
+
+    <!-- Style for the description view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceDescriptionStyle">
+        <item name="android:layout_below">@id/guidance_title</item>
+        <item name="android:layout_toStartOf">@id/guidance_icon</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignWithParentIfMissing">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:gravity">end</item>
+        <item name="android:maxLines">6</item>
+        <item name="android:textColor">#88F1F1F1</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineSpacingExtra">3dp</item>
+    </style>
+
+    <!-- Style for the breadcrumb view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceBreadcrumbStyle">
+        <item name="android:layout_above">@id/guidance_title</item>
+        <item name="android:layout_toStartOf">@id/guidance_icon</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignWithParentIfMissing">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">#88F1F1F1</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <!-- Style for the icon view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceIconStyle">
+        <item name="android:layout_width">140dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignParentEnd">true</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:layout_marginStart">16dp</item>
+        <item name="android:maxHeight">280dp</item>
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+
+    <!-- Style for the container view in a GuidedActionsStylist's default layout. -->
+    <style name="Widget.Leanback.GuidedActionsContainerStyle">
+        <item name="android:layout_width">@dimen/lb_guidedactions_section_width</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_alignParentEnd">true</item>
+        <item name="android:background">@color/lb_guidedactions_background</item>
+        <item name="android:elevation">@dimen/lb_guidedactions_elevation</item>
+    </style>
+
+    <!-- Style for the selector view in a GuidedActionsStylist's default layout. -->
+    <style name="Widget.Leanback.GuidedActionsSelectorStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/lb_guidedactions_selector_min_height</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:alpha">0</item>
+        <item name="android:background">@color/lb_guidedactions_selector_color</item>
+    </style>
+
+    <!-- Style for the vertical grid of actions in a GuidedActionsStylist's default layout. -->
+    <style name="Widget.Leanback.GuidedActionsListStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:focusable">true</item>
+    </style>
+
+
+    <!-- Style for an action's container in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemContainerStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:focusable">true</item>
+        <item name="android:minHeight">@dimen/lb_guidedactions_item_min_height</item>
+        <item name="android:paddingBottom">@dimen/lb_guidedactions_vertical_padding</item>
+        <item name="android:paddingStart">@dimen/lb_guidedactions_item_start_padding</item>
+        <item name="android:paddingEnd">@dimen/lb_guidedactions_item_end_padding</item>
+        <item name="android:paddingTop">@dimen/lb_guidedactions_vertical_padding</item>
+    </style>
+
+    <!-- Style for an action's checkmark in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemCheckmarkStyle">
+        <item name="android:layout_width">@dimen/lb_guidedactions_item_checkmark_diameter</item>
+        <item name="android:layout_height">@dimen/lb_guidedactions_item_checkmark_diameter</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:layout_marginEnd">@dimen/lb_guidedactions_item_delimiter_padding</item>
+        <item name="android:scaleType">center</item>
+        <item name="android:src">@drawable/lb_guidedactions_item_checkmark</item>
+        <item name="android:visibility">invisible</item>
+    </style>
+
+    <!-- Style for an action's icon in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemIconStyle">
+        <item name="android:layout_width">@dimen/lb_guidedactions_item_icon_width</item>
+        <item name="android:layout_height">@dimen/lb_guidedactions_item_icon_height</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:layout_marginEnd">@dimen/lb_guidedactions_item_delimiter_padding</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:visibility">gone</item>
+    </style>
+
+    <!-- Style for an action's text content in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemContentStyle">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">start|center_vertical</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+
+    <!-- Style for an action's title in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemTitleStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:alpha">@string/lb_guidedactions_item_unselected_text_alpha</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+        <item name="android:maxLines">@integer/lb_guidedactions_item_title_min_lines</item>
+        <item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
+        <item name="android:textSize">@dimen/lb_guidedactions_item_title_font_size</item>
+    </style>
+
+    <!-- Style for an action's description in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemDescriptionStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:alpha">@string/lb_guidedactions_item_unselected_description_text_alpha</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+        <item name="android:maxLines">@integer/lb_guidedactions_item_description_min_lines</item>
+        <item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
+        <item name="android:textSize">@dimen/lb_guidedactions_item_description_font_size</item>
+        <item name="android:visibility">gone</item>
+    </style>
+
+    <!-- Style for an action's chevron in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemChevronStyle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:layout_marginStart">@dimen/lb_guidedactions_item_delimiter_padding</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:src">@drawable/lb_ic_guidedactions_item_chevron</item>
+        <item name="android:visibility">gone</item>
+    </style>
+
 </resources>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 457798b..cd331e9 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -22,12 +22,13 @@
         <item name="playbackProgressPrimaryColor">@color/lb_playback_progress_color_no_theme</item>
         <item name="playbackControlsIconHighlightColor">@color/lb_playback_icon_highlight_no_theme</item>
         <item name="defaultBrandColor">@color/lb_default_brand_color</item>
+
+        <item name="android:windowOverscan">true</item>
+        <item name="guidedStepTheme">@style/Theme.Leanback.GuidedStep</item>
     </style>
 
     <style name="Theme.Leanback" parent="Theme.LeanbackBase">
 
-        <item name="android:windowOverscan">true</item>
-
         <item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
         <item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
 
@@ -86,6 +87,7 @@
         <item name="overlayDimMaskColor">@color/lb_view_dim_mask_color</item>
         <item name="overlayDimActiveLevel">@fraction/lb_view_active_level</item>
         <item name="overlayDimDimmedLevel">@fraction/lb_view_dimmed_level</item>
+
     </style>
 
     <style name="Theme.Leanback.Browse" parent="Theme.Leanback">
@@ -100,4 +102,48 @@
         <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
     </style>
 
+    <style name="Theme.Leanback.GuidedStep" parent="Theme.LeanbackBase">
+        <item name="guidedStepThemeFlag">true</item>
+
+        <item name="guidedStepEntryAnimation">@animator/lb_guidedstep_slide_in_from_end</item>
+        <item name="guidedStepExitAnimation">@animator/lb_guidedstep_slide_out_to_start</item>
+        <item name="guidedStepReentryAnimation">@animator/lb_guidedstep_slide_in_from_start</item>
+        <item name="guidedStepReturnAnimation">@animator/lb_guidedstep_slide_out_to_end</item>
+        <item name="guidanceEntryAnimation">@animator/lb_guidance_entry</item>
+        <item name="guidedActionsEntryAnimation">@animator/lb_guidedactions_entry</item>
+
+        <item name="guidanceContainerStyle">@style/Widget.Leanback.GuidanceContainerStyle</item>
+        <item name="guidanceIconStyle">@style/Widget.Leanback.GuidanceIconStyle</item>
+        <item name="guidanceTitleStyle">@style/Widget.Leanback.GuidanceTitleStyle</item>
+        <item name="guidanceBreadcrumbStyle">@style/Widget.Leanback.GuidanceBreadcrumbStyle</item>
+        <item name="guidanceDescriptionStyle">@style/Widget.Leanback.GuidanceDescriptionStyle</item>
+
+        <item name="guidedActionsContainerStyle">@style/Widget.Leanback.GuidedActionsContainerStyle</item>
+        <item name="guidedActionsSelectorStyle">@style/Widget.Leanback.GuidedActionsSelectorStyle</item>
+        <item name="guidedActionsListStyle">@style/Widget.Leanback.GuidedActionsListStyle</item>
+        <item name="guidedActionsSelectorShowAnimation">@animator/lb_guidedactions_selector_show</item>
+        <item name="guidedActionsSelectorHideAnimation">@animator/lb_guidedactions_selector_hide</item>
+
+        <item name="guidedActionItemContainerStyle">@style/Widget.Leanback.GuidedActionItemContainerStyle</item>
+        <item name="guidedActionItemCheckmarkStyle">@style/Widget.Leanback.GuidedActionItemCheckmarkStyle</item>
+        <item name="guidedActionItemIconStyle">@style/Widget.Leanback.GuidedActionItemIconStyle</item>
+        <item name="guidedActionItemContentStyle">@style/Widget.Leanback.GuidedActionItemContentStyle</item>
+        <item name="guidedActionItemTitleStyle">@style/Widget.Leanback.GuidedActionItemTitleStyle</item>
+        <item name="guidedActionItemDescriptionStyle">@style/Widget.Leanback.GuidedActionItemDescriptionStyle</item>
+        <item name="guidedActionItemChevronStyle">@style/Widget.Leanback.GuidedActionItemChevronStyle</item>
+
+        <item name="guidedActionCheckedAnimation">@animator/lb_guidedactions_item_checked</item>
+        <item name="guidedActionUncheckedAnimation">@animator/lb_guidedactions_item_unchecked</item>
+        <item name="guidedActionPressedAnimation">@animator/lb_guidedactions_item_pressed</item>
+        <item name="guidedActionUnpressedAnimation">@animator/lb_guidedactions_item_unpressed</item>
+        <item name="guidedActionEnabledChevronAlpha">@string/lb_guidedactions_item_enabled_chevron_alpha</item>
+        <item name="guidedActionDisabledChevronAlpha">@string/lb_guidedactions_item_disabled_chevron_alpha</item>
+        <item name="guidedActionContentWidth">@dimen/lb_guidedactions_item_text_width</item>
+        <item name="guidedActionContentWidthNoIcon">@dimen/lb_guidedactions_item_text_width_no_icon</item>
+        <item name="guidedActionTitleMinLines">@integer/lb_guidedactions_item_title_min_lines</item>
+        <item name="guidedActionTitleMaxLines">@integer/lb_guidedactions_item_title_max_lines</item>
+        <item name="guidedActionDescriptionMinLines">@integer/lb_guidedactions_item_description_min_lines</item>
+        <item name="guidedActionVerticalPadding">@dimen/lb_guidedactions_vertical_padding</item>
+    </style>
+
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/animation/UntargetableAnimatorSet.java b/v17/leanback/src/android/support/v17/leanback/animation/UntargetableAnimatorSet.java
new file mode 100644
index 0000000..9cdbc0c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/animation/UntargetableAnimatorSet.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.animation;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Custom fragment animations supplied by Fragment.onCreateAnimator have their targets set to the
+ * fragment's main view by the fragment manager.  Sometimes, this isn't what you want; you may be
+ * supplying a heterogeneous collection of animations that already have targets. This class helps
+ * you return such a collection of animations from onCreateAnimator without having their targets
+ * reset.
+ *
+ * Note that one does not simply subclass AnimatorSet and override setTarget() because AnimatorSet
+ * is final.
+ * @hide
+ */
+public class UntargetableAnimatorSet extends Animator {
+
+    private final AnimatorSet mAnimatorSet;
+
+    public UntargetableAnimatorSet(AnimatorSet animatorSet) {
+        mAnimatorSet = animatorSet;
+    }
+
+    @Override
+    public void addListener(Animator.AnimatorListener listener) {
+        mAnimatorSet.addListener(listener);
+    }
+
+    @Override
+    public void cancel() {
+        mAnimatorSet.cancel();
+    }
+
+    @Override
+    public Animator clone() {
+        return mAnimatorSet.clone();
+    }
+
+    @Override
+    public void end() {
+        mAnimatorSet.end();
+    }
+
+    @Override
+    public long getDuration() {
+        return mAnimatorSet.getDuration();
+    }
+
+    @Override
+    public ArrayList<Animator.AnimatorListener> getListeners() {
+        return mAnimatorSet.getListeners();
+    }
+
+    @Override
+    public long getStartDelay() {
+        return mAnimatorSet.getStartDelay();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mAnimatorSet.isRunning();
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mAnimatorSet.isStarted();
+    }
+
+    @Override
+    public void removeAllListeners() {
+        mAnimatorSet.removeAllListeners();
+    }
+
+    @Override
+    public void removeListener(Animator.AnimatorListener listener) {
+        mAnimatorSet.removeListener(listener);
+    }
+
+    @Override
+    public Animator setDuration(long duration) {
+        return mAnimatorSet.setDuration(duration);
+    }
+
+    @Override
+    public void setInterpolator(TimeInterpolator value) {
+        mAnimatorSet.setInterpolator(value);
+    }
+
+    @Override
+    public void setStartDelay(long startDelay) {
+        mAnimatorSet.setStartDelay(startDelay);
+    }
+
+    @Override
+    public void setTarget(Object target) {
+        // ignore
+    }
+
+    @Override
+    public void setupEndValues() {
+        mAnimatorSet.setupEndValues();
+    }
+
+    @Override
+    public void setupStartValues() {
+        mAnimatorSet.setupStartValues();
+    }
+
+    @Override
+    public void start() {
+        mAnimatorSet.start();
+    }
+}
+
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index c6faa18..5965ba6 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -15,6 +15,7 @@
 
 import java.lang.ref.WeakReference;
 
+import android.graphics.PixelFormat;
 import android.support.v17.leanback.R;
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -161,7 +162,7 @@
 
         @Override
         public int getOpacity() {
-            return android.graphics.PixelFormat.OPAQUE;
+            return android.graphics.PixelFormat.TRANSLUCENT;
         }
 
         @Override
@@ -184,30 +185,136 @@
     }
 
     private static class DrawableWrapper {
-        protected int mAlpha;
-        protected Drawable mDrawable;
+        private int mAlpha = FULL_ALPHA;
+        private Drawable mDrawable;
 
         public DrawableWrapper(Drawable drawable) {
             mDrawable = drawable;
-            setAlpha(FULL_ALPHA);
+            updateAlpha();
+        }
+        public DrawableWrapper(DrawableWrapper wrapper, Drawable drawable) {
+            mDrawable = drawable;
+            mAlpha = wrapper.getAlpha();
+            updateAlpha();
         }
         public Drawable getDrawable() {
             return mDrawable;
         }
         public void setAlpha(int alpha) {
             mAlpha = alpha;
-            mDrawable.setAlpha(alpha);
+            updateAlpha();
         }
         public int getAlpha() {
             return mAlpha;
         }
+        private void updateAlpha() {
+            mDrawable.setAlpha(mAlpha);
+        }
         public void setColor(int color) {
             ((ColorDrawable) mDrawable).setColor(color);
         }
     }
 
-    private LayerDrawable mLayerDrawable;
-    private DrawableWrapper mLayerWrapper;
+    private static class TranslucentLayerDrawable extends LayerDrawable {
+        private DrawableWrapper[] mWrapper;
+        private Paint mPaint = new Paint();
+
+        public static TranslucentLayerDrawable create(LayerDrawable layerDrawable) {
+            int numChildren = layerDrawable.getNumberOfLayers();
+            Drawable[] drawables = new Drawable[numChildren];
+            for (int i = 0; i < numChildren; i++) {
+                drawables[i] = layerDrawable.getDrawable(i);
+            }
+            TranslucentLayerDrawable result = new TranslucentLayerDrawable(drawables);
+            for (int i = 0; i < numChildren; i++) {
+                result.setId(i, layerDrawable.getId(i));
+            }
+            return result;
+        }
+
+        public TranslucentLayerDrawable(Drawable[] drawables) {
+            super(drawables);
+            int count = drawables.length;
+            mWrapper = new DrawableWrapper[count];
+            for (int i = 0; i < count; i++) {
+                mWrapper[i] = new DrawableWrapper(drawables[i]);
+            }
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            if (mPaint.getAlpha() != alpha) {
+                mPaint.setAlpha(alpha);
+                invalidateSelf();
+            }
+        }
+
+        @Override
+        public Drawable mutate() {
+            Drawable drawable = super.mutate();
+            int count = getNumberOfLayers();
+            for (int i = 0; i < count; i++) {
+                if (mWrapper[i] != null) {
+                    mWrapper[i] = new DrawableWrapper(mWrapper[i], getDrawable(i));
+                }
+            }
+            return drawable;
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.TRANSLUCENT;
+        }
+
+        @Override
+        public boolean setDrawableByLayerId(int id, Drawable drawable) {
+            return updateDrawable(id, drawable) != null;
+        }
+
+        public DrawableWrapper updateDrawable(int id, Drawable drawable) {
+            super.setDrawableByLayerId(id, drawable);
+            for (int i = 0; i < getNumberOfLayers(); i++) {
+                if (getId(i) == id) {
+                    mWrapper[i] = new DrawableWrapper(drawable);
+                    return mWrapper[i];
+                }
+            }
+            return null;
+        }
+
+        public void clearDrawable(int id, Context context) {
+            for (int i = 0; i < getNumberOfLayers(); i++) {
+                if (getId(i) == id) {
+                    mWrapper[i] = null;
+                    super.setDrawableByLayerId(id, createEmptyDrawable(context));
+                    break;
+                }
+            }
+        }
+
+        public DrawableWrapper findWrapperById(int id) {
+            for (int i = 0; i < getNumberOfLayers(); i++) {
+                if (getId(i) == id) {
+                    return mWrapper[i];
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            if (mPaint.getAlpha() < FULL_ALPHA) {
+                canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(),
+                        mPaint, Canvas.ALL_SAVE_FLAG);
+            }
+            super.draw(canvas);
+            if (mPaint.getAlpha() < FULL_ALPHA) {
+                canvas.restore();
+            }
+        }
+    }
+
+    private TranslucentLayerDrawable mLayerDrawable;
     private DrawableWrapper mImageInWrapper;
     private DrawableWrapper mImageOutWrapper;
     private DrawableWrapper mColorWrapper;
@@ -225,12 +332,16 @@
         }
         @Override
         public void onAnimationEnd(Animator animation) {
+            // mImageInWrapper should be full alpha, but mImageOutWrapper may not be 0 alpha
+            if (mImageOutWrapper != null) {
+                mImageOutWrapper.setAlpha(0);
+                mImageOutWrapper = null;
+            }
             if (mChangeRunnable != null) {
                 long delayMs = getRunnableDelay();
                 if (DEBUG) Log.v(TAG, "animation ended, found change runnable delayMs " + delayMs);
                 mHandler.postDelayed(mChangeRunnable, delayMs);
             }
-            mImageOutWrapper = null;
         }
         @Override
         public void onAnimationCancel(Animator animation) {
@@ -244,6 +355,8 @@
             int fadeInAlpha = (Integer) animation.getAnimatedValue();
             if (mImageInWrapper != null) {
                 mImageInWrapper.setAlpha(fadeInAlpha);
+            } else if (mImageOutWrapper != null) {
+                mImageOutWrapper.setAlpha(255 - fadeInAlpha);
             }
         }
     };
@@ -329,7 +442,7 @@
             drawable = mService.getThemeDrawable(mContext, mThemeDrawableResourceId);
         }
         if (drawable == null) {
-            drawable = createEmptyDrawable();
+            drawable = createEmptyDrawable(mContext);
         }
         return drawable;
     }
@@ -479,18 +592,15 @@
             return;
         }
 
-        mLayerDrawable = (LayerDrawable) ContextCompat.getDrawable(mContext,
-                R.drawable.lb_background).mutate();
+        LayerDrawable layerDrawable = (LayerDrawable)
+                ContextCompat.getDrawable(mContext, R.drawable.lb_background).mutate();
+        mLayerDrawable = TranslucentLayerDrawable.create(layerDrawable);
         mBgView.setBackground(mLayerDrawable);
 
-        mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
+        mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
+        mLayerDrawable.updateDrawable(R.id.background_theme, getThemeDrawable());
 
-        mLayerDrawable.setDrawableByLayerId(R.id.background_theme, getThemeDrawable());
-
-        mLayerWrapper = new DrawableWrapper(mLayerDrawable);
-
-        mColorWrapper = new DrawableWrapper(
-                mLayerDrawable.findDrawableByLayerId(R.id.background_color));
+        mColorWrapper = mLayerDrawable.findWrapperById(R.id.background_color);
 
         updateDimWrapper();
     }
@@ -499,11 +609,10 @@
         if (mDimDrawable == null) {
             mDimDrawable = getDefaultDimLayer();
         }
-        mDimWrapper = new DrawableWrapper(mDimDrawable.getConstantState().newDrawable(
-                mContext.getResources()).mutate());
-        if (mLayerDrawable != null) {
-            mLayerDrawable.setDrawableByLayerId(R.id.background_dim, mDimWrapper.getDrawable());
-        }
+        Drawable dimDrawable = mDimDrawable.getConstantState().newDrawable(
+                mContext.getResources()).mutate();
+        mDimWrapper = mLayerDrawable == null ? null : mLayerDrawable.updateDrawable(
+                R.id.background_dim, dimDrawable);
     }
 
     /**
@@ -589,12 +698,11 @@
     public void release() {
         if (DEBUG) Log.v(TAG, "release " + this);
         if (mLayerDrawable != null) {
-            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
-            mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
-            mLayerDrawable.setDrawableByLayerId(R.id.background_theme, createEmptyDrawable());
+            mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
+            mLayerDrawable.clearDrawable(R.id.background_imageout, mContext);
+            mLayerDrawable.clearDrawable(R.id.background_theme, mContext);
             mLayerDrawable = null;
         }
-        mLayerWrapper = null;
         mImageInWrapper = null;
         mImageOutWrapper = null;
         mColorWrapper = null;
@@ -642,11 +750,11 @@
         showWallpaper(mBackgroundColor == Color.TRANSPARENT);
 
         if (mBackgroundDrawable == null) {
-            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+            mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
         } else {
             if (DEBUG) Log.v(TAG, "Background drawable is available");
-            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
-            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
+            mImageInWrapper = mLayerDrawable.updateDrawable(
+                    R.id.background_imagein, mBackgroundDrawable);
             if (mDimWrapper != null) {
                 mDimWrapper.setAlpha(FULL_ALPHA);
             }
@@ -693,6 +801,14 @@
             }
             mHandler.removeCallbacks(mChangeRunnable);
         }
+
+        if (mLayerDrawable == null) {
+            if (DEBUG) Log.v(TAG, "setDrawableInternal while in released state");
+            mBackgroundDrawable = drawable;
+            updateImmediate();
+            return;
+        }
+
         mChangeRunnable = new ChangeBackgroundRunnable(drawable);
 
         if (mAnimator.isStarted()) {
@@ -761,7 +877,7 @@
     }
 
     private void applyBackgroundChanges() {
-        if (!mAttached || mLayerWrapper == null) {
+        if (!mAttached) {
             return;
         }
 
@@ -775,8 +891,8 @@
 
         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);
+            mImageInWrapper = mLayerDrawable.updateDrawable(
+                    R.id.background_imagein, mBackgroundDrawable);
             if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
             mImageInWrapper.setAlpha(0);
             dimAlpha = FULL_ALPHA;
@@ -842,7 +958,10 @@
         }
 
         private void runTask() {
-            lazyInit();
+            if (mLayerDrawable == null) {
+                if (DEBUG) Log.v(TAG, "runTask while released - should not happen");
+                return;
+            }
 
             if (sameDrawable(mDrawable, mBackgroundDrawable)) {
                 if (DEBUG) Log.v(TAG, "new drawable same as current");
@@ -853,14 +972,11 @@
 
             if (mImageInWrapper != null) {
                 if (DEBUG) Log.v(TAG, "moving image in to image out");
-                mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
-                mImageOutWrapper.setAlpha(FULL_ALPHA);
-
                 // 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());
+                mLayerDrawable.clearDrawable(R.id.background_imagein, mContext);
+                mImageOutWrapper = mLayerDrawable.updateDrawable(R.id.background_imageout,
+                        mImageInWrapper.getDrawable());
                 mImageInWrapper = null;
             }
 
@@ -871,9 +987,9 @@
         }
     }
 
-    private Drawable createEmptyDrawable() {
+    private static Drawable createEmptyDrawable(Context context) {
         Bitmap bitmap = null;
-        return new BitmapDrawable(mContext.getResources(), bitmap);
+        return new BitmapDrawable(context.getResources(), bitmap);
     }
 
     private void showWallpaper(boolean show) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
new file mode 100644
index 0000000..e9c6d37
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.media.AudioManager;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
+ * Presentation (view creation and state animation) is delegated to a {@link
+ * GuidedActionsStylist}, while clients are notified of interactions via
+ * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
+ */
+class GuidedActionAdapter extends RecyclerView.Adapter {
+    private static final String TAG = "GuidedActionAdapter";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Object listening for click events within a {@link GuidedActionAdapter}.
+     */
+    public interface ClickListener {
+
+        /**
+         * Called when the user clicks on an action.
+         */
+        public void onGuidedActionClicked(GuidedAction action);
+    }
+
+    /**
+     * Object listening for focus events within a {@link GuidedActionAdapter}.
+     */
+    public interface FocusListener {
+
+        /**
+         * Called when the user focuses on an action.
+         */
+        public void onGuidedActionFocused(GuidedAction action);
+    }
+
+    /**
+     * View holder containing a {@link GuidedAction}.
+     */
+    private static class ActionViewHolder extends ViewHolder {
+
+        private final GuidedActionsStylist.ViewHolder mStylistViewHolder;
+        private GuidedAction mAction;
+
+        /**
+         * Constructs a view holder that can be associated with a GuidedAction.
+         */
+        public ActionViewHolder(View v, GuidedActionsStylist.ViewHolder subViewHolder) {
+            super(v);
+            mStylistViewHolder = subViewHolder;
+        }
+
+        /**
+         * Retrieves the action associated with this view holder.
+         * @return The GuidedAction associated with this view holder.
+         */
+        public GuidedAction getAction() {
+            return mAction;
+        }
+
+        /**
+         * Sets the action associated with this view holder.
+         * @param action The GuidedAction associated with this view holder.
+         */
+        public void setAction(GuidedAction action) {
+            mAction = action;
+        }
+    }
+
+    private RecyclerView mRecyclerView;
+    private final ActionOnKeyListener mActionOnKeyListener;
+    private final ActionOnFocusListener mActionOnFocusListener;
+    private final List<GuidedAction> mActions;
+    private ClickListener mClickListener;
+    private GuidedActionsStylist mStylist;
+    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (v != null && v.getWindowToken() != null && mClickListener != null) {
+                ActionViewHolder avh = (ActionViewHolder)mRecyclerView.getChildViewHolder(v);
+                GuidedAction action = avh.getAction();
+                if (action.isEnabled() && !action.infoOnly()) {
+                    mClickListener.onGuidedActionClicked(action);
+                }
+            }
+        }
+    };
+
+    /**
+     * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
+     * focus listeners, and the given presenter.
+     * @param actions The list of guided actions this adapter will manage.
+     * @param clickListener The click listener for items in this adapter.
+     * @param focusListener The focus listener for items in this adapter.
+     * @param presenter The presenter that will manage the display of items in this adapter.
+     */
+    public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
+            FocusListener focusListener, GuidedActionsStylist presenter) {
+        super();
+        mActions = new ArrayList<GuidedAction>(actions);
+        mClickListener = clickListener;
+        mStylist = presenter;
+        mActionOnKeyListener = new ActionOnKeyListener(clickListener, mActions);
+        mActionOnFocusListener = new ActionOnFocusListener(focusListener);
+    }
+
+    /**
+     * Sets the list of actions managed by this adapter.
+     * @param actions The list of actions to be managed.
+     */
+    public void setActions(List<GuidedAction> actions) {
+        mActionOnFocusListener.unFocus();
+        mActions.clear();
+        mActions.addAll(actions);
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Returns the count of actions managed by this adapter.
+     * @return The count of actions managed by this adapter.
+     */
+    public int getCount() {
+        return mActions.size();
+    }
+
+    /**
+     * Returns the GuidedAction at the given position in the managed list.
+     * @param position The position of the desired GuidedAction.
+     * @return The GuidedAction at the given position.
+     */
+    public GuidedAction getItem(int position) {
+        return mActions.get(position);
+    }
+
+    /**
+     * Sets the click listener for items managed by this adapter.
+     * @param clickListener The click listener for this adapter.
+     */
+    public void setClickListener(ClickListener clickListener) {
+        mClickListener = clickListener;
+        mActionOnKeyListener.setListener(clickListener);
+    }
+
+    /**
+     * Sets the focus listener for items managed by this adapter.
+     * @param focusListener The focus listener for this adapter.
+     */
+    public void setFocusListener(FocusListener focusListener) {
+        mActionOnFocusListener.setFocusListener(focusListener);
+    }
+
+    /**
+     * Used for serialization only.
+     * @hide
+     */
+    public List<GuidedAction> getActions() {
+        return new ArrayList<GuidedAction>(mActions);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        mRecyclerView = recyclerView;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+        mRecyclerView = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent);
+        View v = vh.view;
+        v.setOnKeyListener(mActionOnKeyListener);
+        v.setOnClickListener(mOnClickListener);
+        v.setOnFocusChangeListener(mActionOnFocusListener);
+
+        return new ActionViewHolder(v, vh);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        if (position >= mActions.size()) {
+            return;
+        }
+        ActionViewHolder avh = (ActionViewHolder)holder;
+        GuidedAction action = mActions.get(position);
+        avh.setAction(action);
+        mStylist.onBindViewHolder(avh.mStylistViewHolder, action);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getItemCount() {
+        return mActions.size();
+    }
+
+    private class ActionOnFocusListener implements View.OnFocusChangeListener {
+
+        private FocusListener mFocusListener;
+        private View mSelectedView;
+
+        ActionOnFocusListener(FocusListener focusListener) {
+            mFocusListener = focusListener;
+        }
+
+        public void setFocusListener(FocusListener focusListener) {
+            mFocusListener = focusListener;
+        }
+
+        public void unFocus() {
+            if (mSelectedView != null) {
+                ViewHolder vh = mRecyclerView.getChildViewHolder(mSelectedView);
+                if (vh != null) {
+                    ActionViewHolder avh = (ActionViewHolder)vh;
+                    mStylist.onAnimateItemFocused(avh.mStylistViewHolder, false);
+                } else {
+                    Log.w(TAG, "RecyclerView returned null view holder",
+                            new Throwable());
+                }
+            }
+        }
+
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            ActionViewHolder avh = (ActionViewHolder)mRecyclerView.getChildViewHolder(v);
+            mStylist.onAnimateItemFocused(avh.mStylistViewHolder, hasFocus);
+            if (hasFocus) {
+                mSelectedView = v;
+                if (mFocusListener != null) {
+                    // We still call onGuidedActionFocused so that listeners can clear
+                    // state if they want.
+                    mFocusListener.onGuidedActionFocused(avh.getAction());
+                }
+            } else {
+                if (mSelectedView == v) {
+                    mSelectedView = null;
+                }
+            }
+        }
+    }
+
+    private class ActionOnKeyListener implements View.OnKeyListener {
+
+        private final List<GuidedAction> mActions;
+        private boolean mKeyPressed = false;
+        private ClickListener mClickListener;
+
+        public ActionOnKeyListener(ClickListener listener,
+                List<GuidedAction> actions) {
+            mClickListener = listener;
+            mActions = actions;
+        }
+
+        public void setListener(ClickListener listener) {
+            mClickListener = listener;
+        }
+
+        private void playSound(View v, int soundEffect) {
+            if (v.isSoundEffectsEnabled()) {
+                Context ctx = v.getContext();
+                AudioManager manager = (AudioManager)ctx.getSystemService(Context.AUDIO_SERVICE);
+                manager.playSoundEffect(soundEffect);
+            }
+        }
+
+        /**
+         * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
+         */
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (v == null || event == null) {
+                return false;
+            }
+            boolean handled = false;
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                case KeyEvent.KEYCODE_BUTTON_X:
+                case KeyEvent.KEYCODE_BUTTON_Y:
+                case KeyEvent.KEYCODE_ENTER:
+
+                    ActionViewHolder avh = (ActionViewHolder)mRecyclerView.getChildViewHolder(v);
+                    GuidedAction action = avh.getAction();
+
+                    if (!action.isEnabled() || action.infoOnly()) {
+                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                            // TODO: requires API 19
+                            //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
+                        }
+                        return true;
+                    }
+
+                    switch (event.getAction()) {
+                        case KeyEvent.ACTION_DOWN:
+                            if (!mKeyPressed) {
+                                mKeyPressed = true;
+
+                                playSound(v, AudioManager.FX_KEY_CLICK);
+
+                                if (DEBUG) {
+                                    Log.d(TAG, "Enter Key down");
+                                }
+
+                                mStylist.onAnimateItemPressed(avh.mStylistViewHolder,
+                                        mKeyPressed);
+                                handled = true;
+                            }
+                            break;
+                        case KeyEvent.ACTION_UP:
+                            if (mKeyPressed) {
+                                mKeyPressed = false;
+
+                                if (DEBUG) {
+                                    Log.d(TAG, "Enter Key up");
+                                }
+
+                                mStylist.onAnimateItemPressed(avh.mStylistViewHolder,
+                                            mKeyPressed);
+                                handleCheckedActions(avh, action);
+                                mClickListener.onGuidedActionClicked(action);
+                                handled = true;
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    break;
+            }
+            return handled;
+        }
+
+        private void handleCheckedActions(ActionViewHolder avh, GuidedAction action) {
+            int actionCheckSetId = action.getCheckSetId();
+            if (actionCheckSetId != GuidedAction.NO_CHECK_SET) {
+                // Find any actions that are checked and are in the same group
+                // as the selected action. Fade their checkmarks out.
+                for (int i = 0, size = mActions.size(); i < size; i++) {
+                    GuidedAction a = mActions.get(i);
+                    if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
+                        a.setChecked(false);
+                        ViewHolder vh = mRecyclerView.findViewHolderForPosition(i);
+                        if (vh != null) {
+                            GuidedActionsStylist.ViewHolder subViewHolder =
+                                    ((ActionViewHolder)avh).mStylistViewHolder;
+                            mStylist.onAnimateItemChecked(subViewHolder, false);
+                        }
+                    }
+                }
+
+                // If we we'ren't already checked, fade our checkmark in.
+                if (!action.isChecked()) {
+                    action.setChecked(true);
+                    mStylist.onAnimateItemChecked(avh.mStylistViewHolder, true);
+                }
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
new file mode 100644
index 0000000..bb40e2e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.animation.UntargetableAnimatorSet;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A GuidedStepFragment is used to guide the user through a decision or series of decisions.
+ * It is composed of a guidance view on the left, a view on the right containing a list of possible
+ * actions, and an optional view in the center to allow for additional configuration.
+ * <p>
+ * <h3>Basic Usage</h3>
+ * <p>
+ * Clients of GuidedStepFragment typically create a custom subclass to attach to their Activities.
+ * This custom subclass provides the information necessary to construct the user interface and
+ * respond to user actions. At a minimum, subclasses should override:
+ * <ul>
+ * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
+ * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
+ * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
+ * </ul>
+ * <p>
+ * <h3>Theming and Stylists</h3>
+ * <p>
+ * GuidedStepFragment delegates its visual styling to classes called stylists. The {@link
+ * GuidanceStylist} is responsible for the left guidance view, while the {@link
+ * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
+ * attributes to derive values associated with the presentation, such as colors, animations, etc.
+ * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
+ * via theming; see their documentation for more information.
+ * <p>
+ * GuidedStepFragments must have access to an appropriate theme in order for the stylists to
+ * function properly.  Specifically, the fragment must receive {@link
+ * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
+ * is set to that theme. Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
+ * theme that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute {@link
+ * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
+ * this theme will be used by GuidedStepFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of GuidedStepFragment may provide a theme through the {@link
+ * #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activty's theme.  (Themes whose parent theme is already set to the guided step theme do not
+ * need to set the guidedStepTheme attribute; if set, it will be ignored.)
+ * <p>
+ * If themes do not provide enough customizability, the stylists themselves may be subclassed and
+ * provided to the GuidedStepFragment through the {@link #onCreateGuidanceStylist} and {@link
+ * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
+ * may override layout files; subclasses may also have more complex logic to determine styling.
+ * <p>
+ * <h3>Guided sequences</h3>
+ * <p>
+ * GuidedStepFragments can be grouped together to provide a guided sequence. GuidedStepFragments
+ * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
+ * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
+ * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
+ * custom animations are properly configured. (Custom animations are triggered automatically when
+ * the fragment stack is subsequently popped by any normal mechanism.)
+ * <p>
+ * <i>Note: Currently GuidedStepFragments grouped in this way must all be defined programmatically,
+ * rather than in XML. This restriction may be removed in the future.</i>
+ * <p>
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
+ * @see GuidanceStylist
+ * @see GuidanceStylist.Guidance
+ * @see GuidedAction
+ * @see GuidedActionsStylist
+ */
+public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.ClickListener,
+        GuidedActionAdapter.FocusListener {
+
+    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
+    private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
+    private static final String EXTRA_ACTION_ENTRY_TRANSITION_ENABLED = "entryTransitionEnabled";
+    private static final String EXTRA_ENTRY_TRANSITION_PERFORMED = "entryTransitionPerformed";
+    private static final String TAG = "GuidedStepFragment";
+    private static final boolean DEBUG = true;
+    private static final int ANIMATION_FRAGMENT_ENTER = 1;
+    private static final int ANIMATION_FRAGMENT_EXIT = 2;
+    private static final int ANIMATION_FRAGMENT_ENTER_POP = 3;
+    private static final int ANIMATION_FRAGMENT_EXIT_POP = 4;
+
+    private int mTheme;
+    private GuidanceStylist mGuidanceStylist;
+    private GuidedActionsStylist mActionsStylist;
+    private GuidedActionAdapter mAdapter;
+    private VerticalGridView mListView;
+    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
+    private int mSelectedIndex = -1;
+    private boolean mEntryTransitionPerformed;
+    private boolean mEntryTransitionEnabled = true;
+
+    public GuidedStepFragment() {
+        // We need to supply the theme before any potential call to onInflate in order
+        // for the defaulting to work properly.
+        mTheme = onProvideTheme();
+        mGuidanceStylist = onCreateGuidanceStylist();
+        mActionsStylist = onCreateActionsStylist();
+    }
+
+    /**
+     * Creates the presenter used to style the guidance panel. The default implementation returns
+     * a basic GuidanceStylist.
+     * @return The GuidanceStylist used in this fragment.
+     */
+    public GuidanceStylist onCreateGuidanceStylist() {
+        return new GuidanceStylist();
+    }
+
+    /**
+     * Creates the presenter used to style the guided actions panel. The default implementation
+     * returns a basic GuidedActionsStylist.
+     * @return The GuidedActionsStylist used in this fragment.
+     */
+    public GuidedActionsStylist onCreateActionsStylist() {
+        return new GuidedActionsStylist();
+    }
+
+    /**
+     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+     * host Activity's theme should be used.
+     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
+     * host Activity's theme.
+     */
+    public int onProvideTheme() {
+        return -1;
+    }
+
+    /**
+     * Returns the information required to provide guidance to the user. This hook is called during
+     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
+     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
+     * returns a Guidance object with empty fields; subclasses should override.
+     * @param savedInstanceState The saved instance state from onCreateView.
+     * @return The Guidance object representing the information used to guide the user.
+     */
+    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("", "", "", null);
+    }
+
+    /**
+     * Fills out the set of actions available to the user. This hook is called during {@link
+     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
+     * @param actions A non-null, empty list ready to be populated.
+     * @param savedInstanceState The saved instance state from onCreate.
+     */
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+    }
+
+    /**
+     * Callback invoked when an action is taken by the user. Subclasses should override in
+     * order to act on the user's decisions.
+     * @param action The chosen action.
+     */
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action is focused (made to be the current selection) by the user.
+     */
+    @Override
+    public void onGuidedActionFocused(GuidedAction action) {
+    }
+
+    /**
+     * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
+     * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom animations.
+     * <p>
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param fragmentManager The FragmentManager to be used in the transaction.
+     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
+     * @return The ID returned by the call FragmentTransaction.replace.
+     */
+    public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) {
+        return add(fragmentManager, fragment, android.R.id.content);
+    }
+
+    // Note, this method used to be public, but I haven't found a good way for a client
+    // to specify an id.
+    private static int add(FragmentManager fm, GuidedStepFragment f, int id) {
+        boolean inGuidedStep = getCurrentGuidedStepFragment(fm) != null;
+        FragmentTransaction ft = fm.beginTransaction();
+
+        if (inGuidedStep) {
+            ft.setCustomAnimations(ANIMATION_FRAGMENT_ENTER,
+                    ANIMATION_FRAGMENT_EXIT, ANIMATION_FRAGMENT_ENTER_POP,
+                    ANIMATION_FRAGMENT_EXIT_POP);
+            ft.addToBackStack(null);
+        }
+        return ft.replace(id, f, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
+    }
+
+    /**
+     * Returns the current GuidedStepFragment on the fragment transaction stack.
+     * @return The current GuidedStepFragment, if any, on the fragment transaction stack.
+     */
+    public static GuidedStepFragment getCurrentGuidedStepFragment(FragmentManager fm) {
+        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
+        if (f instanceof GuidedStepFragment) {
+            return (GuidedStepFragment) f;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the GuidanceStylist that displays guidance information for the user.
+     * @return The GuidanceStylist for this fragment.
+     */
+    public GuidanceStylist getGuidanceStylist() {
+        return mGuidanceStylist;
+    }
+
+    /**
+     * Returns the GuidedActionsStylist that displays the actions the user may take.
+     * @return The GuidedActionsStylist for this fragment.
+     */
+    public GuidedActionsStylist getGuidedActionsStylist() {
+        return mActionsStylist;
+    }
+
+    /**
+     * Returns the list of GuidedActions that the user may take in this fragment.
+     * @return The list of GuidedActions for this fragment.
+     */
+    public List<GuidedAction> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Sets the list of GuidedActions that the user may take in this fragment.
+     * @param actions The list of GuidedActions for this fragment.
+     */
+    public void setActions(List<GuidedAction> actions) {
+        mActions = actions;
+        if (mAdapter != null) {
+            mAdapter.setActions(mActions);
+        }
+    }
+
+    /**
+     * Returns the view corresponding to the action at the indicated position in the list of
+     * actions for this fragment.
+     * @param position The integer position of the action of interest.
+     * @return The View corresponding to the action at the indicated position, or null if that
+     * action is not currently onscreen.
+     */
+    public View getActionItemView(int position) {
+        return mListView.findViewHolderForPosition(position).itemView;
+    }
+
+    /**
+     * Scrolls the action list to the position indicated, selecting that action's view.
+     * @param position The integer position of the action of interest.
+     */
+    public void setSelectedActionPosition(int position) {
+        mListView.setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the position if the currently selected GuidedAction.
+     * @return position The integer position of the currently selected action.
+     */
+    public int getSelectedActionPosition() {
+        return mListView.getSelectedPosition();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (DEBUG) Log.v(TAG, "onCreate");
+        Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments();
+        if (state != null) {
+            if (mSelectedIndex == -1) {
+                mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1);
+            }
+            mEntryTransitionEnabled = state.getBoolean(EXTRA_ACTION_ENTRY_TRANSITION_ENABLED, true);
+            mEntryTransitionPerformed = state.getBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, false);
+        }
+        mActions.clear();
+        onCreateActions(mActions, savedInstanceState);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onCreateView");
+
+        resolveTheme();
+        inflater = getThemeInflater(inflater);
+
+        View v = inflater.inflate(R.layout.lb_guidedstep_fragment, container, false);
+        ViewGroup guidanceContainer = (ViewGroup) v.findViewById(R.id.content_fragment);
+        ViewGroup actionContainer = (ViewGroup) v.findViewById(R.id.action_fragment);
+
+        Guidance guidance = onCreateGuidance(savedInstanceState);
+        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
+        guidanceContainer.addView(guidanceView);
+
+        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
+        actionContainer.addView(actionsView);
+
+        mAdapter = new GuidedActionAdapter(mActions, this, this, mActionsStylist);
+
+        mListView = mActionsStylist.getActionsGridView();
+        mListView.setAdapter(mAdapter);
+        int pos = (mSelectedIndex >= 0 && mSelectedIndex < mActions.size()) ?
+                mSelectedIndex : getFirstCheckedAction();
+        mListView.setSelectedPosition(pos);
+
+        return v;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
+                (mListView != null) ? getSelectedActionPosition() : mSelectedIndex);
+        outState.putBoolean(EXTRA_ACTION_ENTRY_TRANSITION_ENABLED, mEntryTransitionEnabled);
+        outState.putBoolean(EXTRA_ENTRY_TRANSITION_PERFORMED, mEntryTransitionPerformed);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onStart() {
+        if (DEBUG) Log.v(TAG, "onStart");
+        super.onStart();
+        if (isEntryTransitionEnabled() && !mEntryTransitionPerformed) {
+            mEntryTransitionPerformed = true;
+            performEntryTransition();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+        if (DEBUG) Log.v(TAG, "onCreateAnimator: " + transit + " " + enter + " " + nextAnim);
+        View mainView = getView();
+
+        ArrayList<Animator> animators = new ArrayList<Animator>();
+        switch (nextAnim) {
+            case ANIMATION_FRAGMENT_ENTER:
+                mGuidanceStylist.onFragmentEnter(animators);
+                mActionsStylist.onFragmentEnter(animators);
+                break;
+            case ANIMATION_FRAGMENT_EXIT:
+                mGuidanceStylist.onFragmentExit(animators);
+                mActionsStylist.onFragmentExit(animators);
+                break;
+            case ANIMATION_FRAGMENT_ENTER_POP:
+                mGuidanceStylist.onFragmentReenter(animators);
+                mActionsStylist.onFragmentReenter(animators);
+                break;
+            case ANIMATION_FRAGMENT_EXIT_POP:
+                mGuidanceStylist.onFragmentReturn(animators);
+                mActionsStylist.onFragmentReturn(animators);
+                break;
+            default:
+                return super.onCreateAnimator(transit, enter, nextAnim);
+        }
+
+        mEntryTransitionPerformed = true;
+        return createDummyAnimator(mainView, animators);
+    }
+
+    /**
+     * Returns whether entry transitions are enabled for this fragment.
+     * @return Whether entry transitions are enabled for this fragment.
+     */
+    protected boolean isEntryTransitionEnabled() {
+        return mEntryTransitionEnabled;
+    }
+
+    /**
+     * Sets whether entry transitions are enabled for this fragment.
+     * @param enabled Whether to enable entry transitions for this fragment.
+     */
+    protected void setEntryTransitionEnabled(boolean enabled) {
+        mEntryTransitionEnabled = enabled;
+    }
+
+    private boolean isGuidedStepTheme(Context context) {
+        int resId = R.attr.guidedStepThemeFlag;
+        TypedValue typedValue = new TypedValue();
+        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
+        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
+    }
+
+    private void resolveTheme() {
+        boolean hasThemeReference = true;
+        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
+        // replace the theme with its value.
+        Activity activity = getActivity();
+        if (mTheme == -1 && !isGuidedStepTheme(activity)) {
+            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
+            // exists, replace the theme with its value.
+            int resId = R.attr.guidedStepTheme;
+            TypedValue typedValue = new TypedValue();
+            boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true);
+            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
+            if (found) {
+                if (isGuidedStepTheme(new ContextThemeWrapper(activity, typedValue.resourceId))) {
+                    mTheme = typedValue.resourceId;
+                } else {
+                    found = false;
+                }
+            }
+            if (!found) {
+                Log.e(TAG, "GuidedStepFragment does not have an appropriate theme set.");
+            }
+        }
+    }
+
+    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+        if (mTheme == -1) {
+            return inflater;
+        } else {
+            Context ctw = new ContextThemeWrapper(getActivity(), mTheme);
+            return inflater.cloneInContext(ctw);
+        }
+    }
+
+    private int getFirstCheckedAction() {
+        for (int i = 0, size = mActions.size(); i < size; i++) {
+            if (mActions.get(i).isChecked()) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    private void performEntryTransition() {
+        if (DEBUG) Log.v(TAG, "performEntryTransition");
+        final View mainView = getView();
+
+        mainView.setVisibility(View.INVISIBLE);
+
+        ArrayList<Animator> animators = new ArrayList<Animator>();
+        mGuidanceStylist.onActivityEnter(animators);
+        mActionsStylist.onActivityEnter(animators);
+
+        final Animator animator = createDummyAnimator(mainView, animators);
+
+        // We need to defer the animation until the first layout has occurred, as we don't yet
+        // know the final locations of views.
+        mainView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        mainView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                        if (!isAdded()) {
+                            // We have been detached before this could run,
+                            // so just bail
+                            return;
+                        }
+
+                        mainView.setVisibility(View.VISIBLE);
+                        animator.start();
+                    }
+                });
+    }
+
+    private Animator createDummyAnimator(final View v, ArrayList<Animator> animators) {
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(animators);
+        return new UntargetableAnimatorSet(animatorSet);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index 74fa734..47ad9e4 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -183,9 +183,11 @@
             final int count = listView.getChildCount();
             for (int i = 0; i < count; i++) {
                 View view = listView.getChildAt(i);
-                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
                         listView.getChildViewHolder(view);
-                setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                vh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
             }
         }
     }
@@ -309,16 +311,10 @@
         ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
     }
 
-    private static void setOnItemViewSelectedListener(ItemBridgeAdapter.ViewHolder vh,
-            OnItemViewSelectedListener listener) {
-        ((RowPresenter) vh.getPresenter()).setOnItemViewSelectedListener(listener);
-    }
-
     private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
             new ItemBridgeAdapter.AdapterListener() {
         @Override
         public void onAddPresenter(Presenter presenter, int type) {
-            ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener);
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onAddPresenter(presenter, type);
             }
@@ -350,9 +346,10 @@
             // but again it should use the unchanged mExpand value,  so we don't need do any
             // thing in onBind.
             setRowViewExpanded(vh, mExpand);
-            setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
             RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
             RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowVh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+            rowVh.setOnItemViewClickedListener(mOnItemViewClickedListener);
             rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onAttachedToWindow(vh);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index 2f8deff..029ddbd 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -185,9 +185,11 @@
             final int count = listView.getChildCount();
             for (int i = 0; i < count; i++) {
                 View view = listView.getChildAt(i);
-                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
                         listView.getChildViewHolder(view);
-                setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                vh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
             }
         }
     }
@@ -311,16 +313,10 @@
         ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
     }
 
-    private static void setOnItemViewSelectedListener(ItemBridgeAdapter.ViewHolder vh,
-            OnItemViewSelectedListener listener) {
-        ((RowPresenter) vh.getPresenter()).setOnItemViewSelectedListener(listener);
-    }
-
     private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
             new ItemBridgeAdapter.AdapterListener() {
         @Override
         public void onAddPresenter(Presenter presenter, int type) {
-            ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener);
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onAddPresenter(presenter, type);
             }
@@ -352,9 +348,10 @@
             // but again it should use the unchanged mExpand value,  so we don't need do any
             // thing in onBind.
             setRowViewExpanded(vh, mExpand);
-            setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
             RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
             RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowVh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+            rowVh.setOnItemViewClickedListener(mOnItemViewClickedListener);
             rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onAttachedToWindow(vh);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
index 9259a1d..c686a36 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -76,13 +76,14 @@
 
         @Override
         public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) {
-            if (getOnItemViewClickedListener() != null || mActionClickedListener != null) {
+            if (mViewHolder.getOnItemViewClickedListener() != null ||
+                    mActionClickedListener != null) {
                 ibvh.getPresenter().setOnClickListener(
                         ibvh.getViewHolder(), new View.OnClickListener() {
                             @Override
                             public void onClick(View v) {
-                                if (getOnItemViewClickedListener() != null) {
-                                    getOnItemViewClickedListener().onItemClicked(
+                                if (mViewHolder.getOnItemViewClickedListener() != null) {
+                                    mViewHolder.getOnItemViewClickedListener().onItemClicked(
                                             ibvh.getViewHolder(), ibvh.getItem(),
                                             mViewHolder, mViewHolder.getRow());
                                 }
@@ -95,7 +96,8 @@
         }
         @Override
         public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) {
-            if (getOnItemViewClickedListener() != null || mActionClickedListener != null) {
+            if (mViewHolder.getOnItemViewClickedListener() != null ||
+                    mActionClickedListener != null) {
                 ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null);
             }
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java b/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
new file mode 100644
index 0000000..8bd0007
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.animation.Animator;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * FragmentAnimationProvider supplies animations for use during a fragment's onCreateAnimator
+ * callback. Animators added here will be added to an animation set and played together. This
+ * allows presenters used by a fragment to control their own fragment lifecycle animations.
+ */
+public interface FragmentAnimationProvider {
+
+    /**
+     * Animates the entry of the fragment in the case where the activity is first being presented.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onActivityEnter(@NonNull List<Animator> animators);
+
+    /**
+     * Animates the exit of the fragment in the case where the activity is about to pause.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onActivityExit(@NonNull List<Animator> animators);
+
+    /**
+     * Animates the entry of the fragment in the case where there is a previous step fragment
+     * participating in the animation. Entry occurs when the fragment is preparing to be shown
+     * as it is pushed onto the back stack.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onFragmentEnter(@NonNull List<Animator> animators);
+
+    /**
+     * Animates the exit of the fragment in the case where there is a previous step fragment
+     * participating in the animation. Exit occurs when the fragment is preparing to be removed,
+     * hidden, or detached due to pushing another fragment onto the back stack.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onFragmentExit(@NonNull List<Animator> animators);
+
+    /**
+     * Animates the re-entry of the fragment in the case where there is a previous step fragment
+     * participating in the animation. Re-entry occurs when the fragment is preparing to be shown
+     * due to popping the back stack.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onFragmentReenter(@NonNull List<Animator> animators);
+
+    /**
+     * Animates the return of the fragment in the case where there is a previous step fragment
+     * participating in the animation. Return occurs when the fragment is preparing to be removed,
+     * hidden, or detached due to popping the back stack.
+     * @param animators A list of animations to which this provider's animations should be added.
+     */
+    public abstract void onFragmentReturn(@NonNull List<Animator> animators);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 3858b34..b0849e9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -943,7 +943,8 @@
             // if focus position is never set before,  initialize it to 0
             mFocusPosition = 0;
         }
-        if (!mState.didStructureChange() && !mForceFullLayout && mGrid != null) {
+        if (!mState.didStructureChange() && mGrid.getFirstVisibleIndex() >= 0 &&
+                !mForceFullLayout && mGrid != null) {
             updateScrollController();
             updateScrollSecondAxis();
             mGrid.setMargin(mMarginPrimary);
@@ -1642,15 +1643,14 @@
             fastRelayout();
             // appends items till focus position.
             if (mFocusPosition != NO_POSITION) {
-                View focusView;
-                while ((focusView = findViewByPosition(mFocusPosition)) == null) {
-                    appendOneColumnVisibleItems();
-                }
-                if (scrollToFocus) {
-                    scrollToView(focusView, false);
-                }
-                if (hadFocus) {
-                    focusView.requestFocus();
+                View focusView = findViewByPosition(mFocusPosition);
+                if (focusView != null) {
+                    if (scrollToFocus) {
+                        scrollToView(focusView, false);
+                    }
+                    if (hadFocus) {
+                        focusView.requestFocus();
+                    }
                 }
             }
         } else {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
new file mode 100644
index 0000000..8d12510
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.R;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * GuidanceStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment}
+ * to display contextual information for the decision(s) required at that step.
+ * <p>
+ * Many aspects of the base GuidanceStylist can be customized through theming; see the theme
+ * attributes below. Note that these attributes are not set on individual elements in layout
+ * XML, but instead would be set in a custom theme. See
+ * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>
+ * for more information.
+ * <p>
+ * If these hooks are insufficient, this class may also be subclassed. Subclasses
+ * may wish to override the {@link #onProvideLayoutId} method to change the layout file used to
+ * display the guidance; more complex layouts may be supported by also providing a subclass of
+ * {@link GuidanceStylist.Guidance} with extra fields.
+ * <p>
+ * Note: If an alternate layout is provided, the following view IDs should be used to refer to base
+ * elements:
+ * <ul>
+ * <li>{@link android.support.v17.leanback.R.id#guidance_title}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidance_description}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidance_breadcrumb}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidance_icon}</li>
+ * </ul><p>
+ * View IDs are allowed to be missing, in which case the corresponding views will be null.
+ *
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceEntryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepEntryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepExitAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReentryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepReturnAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceContainerStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceTitleStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceDescriptionStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceBreadcrumbStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidanceIconStyle
+ * @see android.support.v17.leanback.app.GuidedStepFragment
+ * @see GuidanceStylist.Guidance
+ */
+public class GuidanceStylist implements FragmentAnimationProvider {
+
+    /**
+     * A data class representing contextual information for a {@link
+     * android.support.v17.leanback.app.GuidedStepFragment}. Guidance consists of a short title,
+     * a longer description, a breadcrumb to help with global navigation (often indicating where
+     * the back button will lead), and an optional icon.  All this information is intended to
+     * provide users with the appropriate context to make the decision(s) required by the current
+     * step.
+     * <p>
+     * Clients may provide a subclass of this if they wish to remember auxiliary data for use in
+     * a customized GuidanceStylist.
+     */
+    public static class Guidance {
+        private final String mTitle;
+        private final String mDescription;
+        private final String mBreadcrumb;
+        private final Drawable mIconDrawable;
+
+        /**
+         * Constructs a Guidance object with the specified title, description, breadcrumb, and
+         * icon drawable.
+         * @param title The title for the current guided step.
+         * @param description The description for the current guided step.
+         * @param breadcrumb The breadcrumb for the current guided step.
+         * @param icon The icon drawable representing the current guided step.
+         */
+        public Guidance(String title, String description, String breadcrumb, Drawable icon) {
+            mBreadcrumb = breadcrumb;
+            mTitle = title;
+            mDescription = description;
+            mIconDrawable = icon;
+        }
+
+        /**
+         * Returns the title specified when this Guidance was constructed.
+         * @return The title for this Guidance.
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+
+        /**
+         * Returns the description specified when this Guidance was constructed.
+         * @return The description for this Guidance.
+         */
+        public String getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Returns the breadcrumb specified when this Guidance was constructed.
+         * @return The breadcrumb for this Guidance.
+         */
+        public String getBreadcrumb() {
+            return mBreadcrumb;
+        }
+
+        /**
+         * Returns the icon drawable specified when this Guidance was constructed.
+         * @return The icon for this Guidance.
+         */
+        public Drawable getIconDrawable() {
+            return mIconDrawable;
+        }
+    }
+
+    private TextView mTitleView;
+    private TextView mDescriptionView;
+    private TextView mBreadcrumbView;
+    private ImageView mIconView;
+
+    /**
+     * Creates an appropriately configured view for the given Guidance, using the provided
+     * inflater and container.
+     * <p>
+     * <i>Note: Does not actually add the created view to the container; the caller should do
+     * this.</i>
+     * @param inflater The layout inflater to be used when constructing the view.
+     * @param container The view group to be passed in the call to
+     * <code>LayoutInflater.inflate</code>.
+     * @param guidance The guidance data for the view.
+     * @return The view to be added to the caller's view hierarchy.
+     */
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Guidance guidance) {
+        View guidanceView = inflater.inflate(onProvideLayoutId(), container, false);
+        mTitleView = (TextView) guidanceView.findViewById(R.id.guidance_title);
+        mBreadcrumbView = (TextView) guidanceView.findViewById(R.id.guidance_breadcrumb);
+        mDescriptionView = (TextView) guidanceView.findViewById(R.id.guidance_description);
+        mIconView = (ImageView) guidanceView.findViewById(R.id.guidance_icon);
+
+        // We allow any of the cached subviews to be null, so that subclasses can choose not to
+        // display a particular piece of information.
+        if (mTitleView != null) {
+            mTitleView.setText(guidance.getTitle());
+        }
+        if (mBreadcrumbView != null) {
+            mBreadcrumbView.setText(guidance.getBreadcrumb());
+        }
+        if (mDescriptionView != null) {
+            mDescriptionView.setText(guidance.getDescription());
+        }
+        if (mIconView != null) {
+            mIconView.setImageDrawable(guidance.getIconDrawable());
+        }
+        return guidanceView;
+    }
+
+    /**
+     * Provides the resource ID of the layout defining the guidance view. Subclasses may override
+     * to provide their own customized layouts. The base implementation returns
+     * {@link android.support.v17.leanback.R.layout#lb_guidance}. If overridden, the substituted
+     * layout should contain matching IDs for any views that should be managed by the base class;
+     * this can be achieved by starting with a copy of the base layout file.
+     * @return The resource ID of the layout to be inflated to define the guidance view.
+     */
+    public int onProvideLayoutId() {
+        return R.layout.lb_guidance;
+    }
+
+    /**
+     * Returns the view displaying the title of the guidance.
+     * @return The text view object for the title.
+     */
+    public TextView getTitleView() {
+        return mTitleView;
+    }
+
+    /**
+     * Returns the view displaying the description of the guidance.
+     * @return The text view object for the description.
+     */
+    public TextView getDescriptionView() {
+        return mDescriptionView;
+    }
+
+    /**
+     * Returns the view displaying the breadcrumb of the guidance.
+     * @return The text view object for the breadcrumb.
+     */
+    public TextView getBreadcrumbView() {
+        return mBreadcrumbView;
+    }
+
+    /**
+     * Returns the view displaying the icon of the guidance.
+     * @return The image view object for the icon.
+     */
+    public ImageView getIconView() {
+        return mIconView;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityEnter(@NonNull List<Animator> animators) {
+        addAnimator(animators, mTitleView, R.attr.guidanceEntryAnimation);
+        addAnimator(animators, mBreadcrumbView, R.attr.guidanceEntryAnimation);
+        addAnimator(animators, mDescriptionView, R.attr.guidanceEntryAnimation);
+        addAnimator(animators, mIconView, R.attr.guidanceEntryAnimation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityExit(@NonNull List<Animator> animators) {}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentEnter(@NonNull List<Animator> animators) {
+        addAnimator(animators, mTitleView, R.attr.guidedStepEntryAnimation);
+        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepEntryAnimation);
+        addAnimator(animators, mDescriptionView, R.attr.guidedStepEntryAnimation);
+        addAnimator(animators, mIconView, R.attr.guidedStepEntryAnimation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentExit(@NonNull List<Animator> animators) {
+        addAnimator(animators, mTitleView, R.attr.guidedStepExitAnimation);
+        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepExitAnimation);
+        addAnimator(animators, mDescriptionView, R.attr.guidedStepExitAnimation);
+        addAnimator(animators, mIconView, R.attr.guidedStepExitAnimation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentReenter(@NonNull List<Animator> animators) {
+        addAnimator(animators, mTitleView, R.attr.guidedStepReentryAnimation);
+        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepReentryAnimation);
+        addAnimator(animators, mDescriptionView, R.attr.guidedStepReentryAnimation);
+        addAnimator(animators, mIconView, R.attr.guidedStepReentryAnimation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentReturn(@NonNull List<Animator> animators) {
+        addAnimator(animators, mTitleView, R.attr.guidedStepReturnAnimation);
+        addAnimator(animators, mBreadcrumbView, R.attr.guidedStepReturnAnimation);
+        addAnimator(animators, mDescriptionView, R.attr.guidedStepReturnAnimation);
+        addAnimator(animators, mIconView, R.attr.guidedStepReturnAnimation);
+    }
+
+    private void addAnimator(List<Animator> animators, View v, int attrId) {
+        if (v != null) {
+            Context ctx = v.getContext();
+            TypedValue typedValue = new TypedValue();
+            ctx.getTheme().resolveAttribute(attrId, typedValue, true);
+            Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
+            animator.setTarget(v);
+            animators.add(animator);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
new file mode 100644
index 0000000..e4db2eb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+/**
+ * A data class which represents an action within a {@link
+ * android.support.v17.leanback.app.GuidedStepFragment}. GuidedActions contain at minimum a title
+ * and a description, and typically also an icon.
+ * <p>
+ * A GuidedAction typically represents a single action a user may take, but may also represent a
+ * possible choice out of a group of mutually exclusive choices (similar to radio buttons), or an
+ * information-only label (in which case the item cannot be clicked).
+ * <p>
+ * GuidedActions may optionally be checked. They may also indicate that they will request further
+ * user input on selection, in which case they will be displayed with a chevron indicator.
+ */
+public class GuidedAction extends Action {
+
+    private static final String TAG = "GuidedAction";
+
+    public static final int NO_DRAWABLE = 0;
+    public static final int NO_CHECK_SET = 0;
+    public static final int DEFAULT_CHECK_SET_ID = 1;
+
+    /**
+     * Builds a {@link GuidedAction} object.
+     */
+    public static class Builder {
+        private long mId;
+        private String mTitle;
+        private String mDescription;
+        private Drawable mIcon;
+        private boolean mChecked;
+        private boolean mMultilineDescription;
+        private boolean mHasNext;
+        private boolean mInfoOnly;
+        private int mCheckSetId = NO_CHECK_SET;
+        private boolean mEnabled = true;
+        private Intent mIntent;
+
+        /**
+         * Builds the GuidedAction corresponding to this Builder.
+         * @return the GuidedAction as configured through this Builder.
+         */
+        public GuidedAction build() {
+            GuidedAction action = new GuidedAction();
+            // Base Action values
+            action.setId(mId);
+            action.setLabel1(mTitle);
+            action.setLabel2(mDescription);
+            action.setIcon(mIcon);
+
+            // Subclass values
+            action.mIntent = mIntent;
+            action.mChecked = mChecked;
+            action.mCheckSetId = mCheckSetId;
+            action.mMultilineDescription = mMultilineDescription;
+            action.mHasNext = mHasNext;
+            action.mInfoOnly = mInfoOnly;
+            action.mEnabled = mEnabled;
+            return action;
+        }
+
+        /**
+         * Sets the ID associated with this action.  The ID can be any value the client wishes;
+         * it is typically used to determine what to do when an action is clicked.
+         * @param id The ID to associate with this action.
+         */
+        public Builder id(long id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets the title for this action.  The title is typically a short string indicating the
+         * action to be taken on click, e.g. "Continue" or "Cancel".
+         * @param title The title for this action.
+         */
+        public Builder title(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the description for this action.  The description is typically a longer string
+         * providing extra information on what the action will do.
+         * @param description The description for this action.
+         */
+        public Builder description(String description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets the intent associated with this action.  Clients would typically fire this intent
+         * directly when the action is clicked.
+         * @param intent The intent associated with this action.
+         */
+        public Builder intent(Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the action's icon drawable.
+         * @param icon The drawable for the icon associated with this action.
+         */
+        public Builder icon(Drawable icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the action's icon drawable by retrieving it by resource ID from the specified
+         * context. This is a convenience function that simply looks up the drawable and calls
+         * {@link #icon}.
+         * @param iconResourceId The resource ID for the icon associated with this action.
+         * @param context The context whose resource ID should be retrieved.
+         */
+        public Builder iconResourceId(int iconResourceId, Context context) {
+            return icon(context.getResources().getDrawable(iconResourceId));
+        }
+
+        /**
+         * Indicates whether this action is initially checked.
+         * @param checked Whether this action is checked.
+         */
+        public Builder checked(boolean checked) {
+            mChecked = checked;
+            return this;
+        }
+
+        /**
+         * Indicates whether this action is part of a single-select group similar to radio buttons.
+         * When one item in a check set is checked, all others with the same check set ID will be
+         * unchecked automatically.
+         * @param checkSetId The check set ID, or {@link #NO_CHECK_SET) to indicate no check set.
+         */
+        public Builder checkSetId(int checkSetId) {
+            mCheckSetId = checkSetId;
+            return this;
+        }
+
+        /**
+         * Indicates whether the title and description are long, and should be displayed
+         * appropriately.
+         * @param multilineDescription Whether this action has a multiline description.
+         */
+        public Builder multilineDescription(boolean multilineDescription) {
+            mMultilineDescription = multilineDescription;
+            return this;
+        }
+
+        /**
+         * Indicates whether this action has a next state and should display a chevron.
+         * @param hasNext Whether this action has a next state.
+         */
+        public Builder hasNext(boolean hasNext) {
+            mHasNext = hasNext;
+            return this;
+        }
+
+        /**
+         * Indicates whether this action is for information purposes only and cannot be clicked.
+         * @param infoOnly Whether this action has a next state.
+         */
+        public Builder infoOnly(boolean infoOnly) {
+            mInfoOnly = infoOnly;
+            return this;
+        }
+
+        /**
+         * Indicates whether this action is enabled.  If not enabled, an action cannot be clicked.
+         * @param enabled Whether the action is enabled.
+         */
+        public Builder enabled(boolean enabled) {
+            mEnabled = enabled;
+            return this;
+        }
+    }
+
+    private boolean mChecked;
+    private boolean mMultilineDescription;
+    private boolean mHasNext;
+    private boolean mInfoOnly;
+    private int mCheckSetId;
+    private boolean mEnabled;
+
+    private Intent mIntent;
+
+    private GuidedAction() {
+        super(0);
+    }
+
+    /**
+     * Returns the title of this action.
+     * @return The title set when this action was built.
+     */
+    public CharSequence getTitle() {
+        return getLabel1();
+    }
+
+    /**
+     * Returns the description of this action.
+     * @return The description set when this action was built.
+     */
+    public CharSequence getDescription() {
+        return getLabel2();
+    }
+
+    /**
+     * Returns the intent associated with this action.
+     * @return The intent set when this action was built.
+     */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Returns whether this action is checked.
+     * @return true if the action is currently checked, false otherwise.
+     */
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    /**
+     * Sets whether this action is checked.
+     * @param checked Whether this action should be checked.
+     */
+    public void setChecked(boolean checked) {
+        mChecked = checked;
+    }
+
+    /**
+     * Returns the check set id this action is a part of. All actions in the
+     * same list with the same check set id are considered linked. When one
+     * of the actions within that set is selected, that action becomes
+     * checked, while all the other actions become unchecked.
+     *
+     * @return an integer representing the check set this action is a part of, or
+     *         {@link #NO_CHECK_SET} if this action isn't a part of a check set.
+     */
+    public int getCheckSetId() {
+        return mCheckSetId;
+    }
+
+    /**
+     * Returns whether this action is has a multiline description.
+     * @return true if the action was constructed as having a multiline description, false
+     * otherwise.
+     */
+    public boolean hasMultilineDescription() {
+        return mMultilineDescription;
+    }
+
+    /**
+     * Returns whether this action is enabled.
+     * @return true if the action is currently enabled, false otherwise.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Sets whether this action is enabled.
+     * @param enabled Whether this action should be enabled.
+     */
+    public void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+    }
+
+    /**
+     * Returns whether this action will request further user input when selected, such as showing
+     * another GuidedStepFragment or launching a new activity. Configured during construction.
+     * @return true if the action will request further user input when selected, false otherwise.
+     */
+    public boolean hasNext() {
+        return mHasNext;
+    }
+
+    /**
+     * Returns whether the action will only display information and is thus not clickable. If both
+     * this and {@link #hasNext()} are true, infoOnly takes precedence. The default is false. For
+     * example, this might represent e.g. the amount of storage a document uses, or the cost of an
+     * app.
+     * @return true if will only display information, false otherwise.
+     */
+    public boolean infoOnly() {
+        return mInfoOnly;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
new file mode 100644
index 0000000..3562234
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.animation.DecelerateInterpolator;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * GuidedActionsStylist is used within a GuidedStepFragment to supply the right-side panel
+ * where users can take actions. It consists of a container for the list of actions, and a
+ * stationary selector view that indicates visually the location of focus.
+ * <p>
+ * Many aspects of the base GuidedActionsStylist can be customized through theming; see the
+ * theme attributes below. Note that these attributes are not set on individual elements in layout
+ * XML, but instead would be set in a custom theme. See
+ * <a href="http://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>
+ * for more information.
+ * <p>
+ * If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to
+ * override the {@link #onProvideLayoutId} method to change the layout used to display the
+ * list container and selector, or the {@link #onProvideItemLayoutId} method to change the layout
+ * used to display each action.
+ * <p>
+ * Note: If an alternate list layout is provided, the following view IDs must be supplied:
+ * <ul>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_selector}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_list}</li>
+ * </ul><p>
+ * These view IDs must be present in order for the stylist to function. The list ID must correspond
+ * to a {@link VerticalGridView} or subclass.
+ * <p>
+ * If an alternate item layout is provided, the following view IDs should be used to refer to base
+ * elements:
+ * <ul>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_content}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_title}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_description}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_icon}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_checkmark}</li>
+ * <li>{@link android.support.v17.leanback.R.id#guidedactions_item_chevron}</li>
+ * </ul><p>
+ * These view IDs are allowed to be missing, in which case the corresponding views in {@link
+ * GuidedActionsStylist.ViewHolder} will be null.
+ *
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsEntryAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorShowAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorHideAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsContainerStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsListStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContainerStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemCheckmarkStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemIconStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContentStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemTitleStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemDescriptionStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemChevronStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionCheckedAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUncheckedAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionPressedAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionUnpressedAnimation
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionEnabledChevronAlpha
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDisabledChevronAlpha
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidth
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthNoIcon
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMinLines
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionTitleMaxLines
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionDescriptionMinLines
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionVerticalPadding
+ * @see android.support.v17.leanback.app.GuidedStepFragment
+ * @see GuidedAction
+ */
+public class GuidedActionsStylist implements FragmentAnimationProvider {
+
+    /**
+     * ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link
+     * GuidedActionsStylist} may also wish to subclass this in order to add fields.
+     * @see GuidedAction
+     */
+    public static class ViewHolder {
+
+        public final View view;
+
+        private View mContentView;
+        private TextView mTitleView;
+        private TextView mDescriptionView;
+        private ImageView mIconView;
+        private ImageView mCheckmarkView;
+        private ImageView mChevronView;
+
+        /**
+         * Constructs an ViewHolder and caches the relevant subviews.
+         */
+        public ViewHolder(View v) {
+            view = v;
+
+            mContentView = v.findViewById(R.id.guidedactions_item_content);
+            mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title);
+            mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description);
+            mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon);
+            mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark);
+            mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron);
+        }
+
+        /**
+         * Returns the content view within this view holder's view, where title and description are
+         * shown.
+         */
+        public View getContentView() {
+            return mContentView;
+        }
+
+        /**
+         * Returns the title view within this view holder's view.
+         */
+        public TextView getTitleView() {
+            return mTitleView;
+        }
+
+        /**
+         * Returns the description view within this view holder's view.
+         */
+        public TextView getDescriptionView() {
+            return mDescriptionView;
+        }
+
+        /**
+         * Returns the icon view within this view holder's view.
+         */
+        public ImageView getIconView() {
+            return mIconView;
+        }
+
+        /**
+         * Returns the checkmark view within this view holder's view.
+         */
+        public ImageView getCheckmarkView() {
+            return mCheckmarkView;
+        }
+
+        /**
+         * Returns the chevron view within this view holder's view.
+         */
+        public ImageView getChevronView() {
+            return mChevronView;
+        }
+
+    }
+
+    private static String TAG = "GuidedActionsStylist";
+
+    protected View mMainView;
+    protected VerticalGridView mActionsGridView;
+    protected View mSelectorView;
+
+    // Cached values from resources
+    private float mEnabledChevronAlpha;
+    private float mDisabledChevronAlpha;
+    private int mContentWidth;
+    private int mContentWidthNoIcon;
+    private int mTitleMinLines;
+    private int mTitleMaxLines;
+    private int mDescriptionMinLines;
+    private int mVerticalPadding;
+    private int mDisplayHeight;
+
+    /**
+     * Creates a view appropriate for displaying a list of GuidedActions, using the provided
+     * inflater and container.
+     * <p>
+     * <i>Note: Does not actually add the created view to the container; the caller should do
+     * this.</i>
+     * @param inflater The layout inflater to be used when constructing the view.
+     * @param container The view group to be passed in the call to
+     * <code>LayoutInflater.inflate</code>.
+     * @return The view to be added to the caller's view hierarchy.
+     */
+    public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+        mMainView = inflater.inflate(onProvideLayoutId(), container, false);
+        mSelectorView = mMainView.findViewById(R.id.guidedactions_selector);
+        if (mMainView instanceof VerticalGridView) {
+            mActionsGridView = (VerticalGridView) mMainView;
+        } else {
+            mActionsGridView = (VerticalGridView) mMainView.findViewById(R.id.guidedactions_list);
+            if (mActionsGridView == null) {
+                throw new IllegalStateException("No ListView exists.");
+            }
+            mActionsGridView.setWindowAlignmentOffset(0);
+            mActionsGridView.setWindowAlignmentOffsetPercent(50f);
+            mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+            if (mSelectorView != null) {
+                mActionsGridView.setOnScrollListener(new
+                        SelectorAnimator(mSelectorView, mActionsGridView));
+            }
+        }
+
+        mActionsGridView.requestFocusFromTouch();
+
+        if (mSelectorView != null) {
+            // ALlow focus to move to other views
+            mActionsGridView.getViewTreeObserver().addOnGlobalFocusChangeListener(
+                    new ViewTreeObserver.OnGlobalFocusChangeListener() {
+                        private boolean mChildFocused;
+
+                        @Override
+                        public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+                            View focusedChild = mActionsGridView.getFocusedChild();
+                            if (focusedChild == null) {
+                                mSelectorView.setVisibility(View.INVISIBLE);
+                                mChildFocused = false;
+                            } else if (!mChildFocused) {
+                                mChildFocused = true;
+                                mSelectorView.setVisibility(View.VISIBLE);
+                                updateSelectorView(focusedChild);
+                            }
+                        }
+                    });
+        }
+
+        // Cache widths, chevron alpha values, max and min text lines, etc
+        Context ctx = mMainView.getContext();
+        TypedValue val = new TypedValue();
+        mEnabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionEnabledChevronAlpha);
+        mDisabledChevronAlpha = getFloat(ctx, val, R.attr.guidedActionDisabledChevronAlpha);
+        mContentWidth = getDimension(ctx, val, R.attr.guidedActionContentWidth);
+        mContentWidthNoIcon = getDimension(ctx, val, R.attr.guidedActionContentWidthNoIcon);
+        mTitleMinLines = getInteger(ctx, val, R.attr.guidedActionTitleMinLines);
+        mTitleMaxLines = getInteger(ctx, val, R.attr.guidedActionTitleMaxLines);
+        mDescriptionMinLines = getInteger(ctx, val, R.attr.guidedActionDescriptionMinLines);
+        mVerticalPadding = getDimension(ctx, val, R.attr.guidedActionVerticalPadding);
+        mDisplayHeight = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay().getHeight();
+
+        return mMainView;
+    }
+
+    /**
+     * Returns the VerticalGridView that displays the list of GuidedActions.
+     * @return The VerticalGridView for this presenter.
+     */
+    public VerticalGridView getActionsGridView() {
+        return mActionsGridView;
+    }
+
+    /**
+     * Provides the resource ID of the layout defining the host view for the list of guided actions.
+     * Subclasses may override to provide their own customized layouts. The base implementation
+     * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions}. If overridden, the
+     * substituted layout should contain matching IDs for any views that should be managed by the
+     * base class; this can be achieved by starting with a copy of the base layout file.
+     * @return The resource ID of the layout to be inflated to define the host view for the list
+     * of GuidedActions.
+     */
+    public int onProvideLayoutId() {
+        return R.layout.lb_guidedactions;
+    }
+
+    /**
+     * Provides the resource ID of the layout defining the view for an individual guided actions.
+     * Subclasses may override to provide their own customized layouts. The base implementation
+     * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
+     * the substituted layout should contain matching IDs for any views that should be managed by
+     * the base class; this can be achieved by starting with a copy of the base layout file.
+     * @return The resource ID of the layout to be inflated to define the view to display an
+     * individual GuidedAction.
+     */
+    public int onProvideItemLayoutId() {
+        return R.layout.lb_guidedactions_item;
+    }
+
+    /**
+     * Constructs a {@link ViewHolder} capable of representing {@link GuidedAction}s. Subclasses
+     * may choose to return a subclass of ViewHolder.
+     * <p>
+     * <i>Note: Should not actually add the created view to the parent; the caller will do
+     * this.</i>
+     * @param parent The view group to be used as the parent of the new view.
+     * @return The view to be added to the caller's view hierarchy.
+     */
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        View v = inflater.inflate(onProvideItemLayoutId(), parent, false);
+        return new ViewHolder(v);
+    }
+
+    /**
+     * Binds a {@link ViewHolder} to a particular {@link GuidedAction}.
+     * @param vh The view holder to be associated with the given action.
+     * @param action The guided action to be displayed by the view holder's view.
+     * @return The view to be added to the caller's view hierarchy.
+     */
+    public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
+
+        if (vh.mTitleView != null) {
+            vh.mTitleView.setText(action.getTitle());
+        }
+        if (vh.mDescriptionView != null) {
+            vh.mDescriptionView.setText(action.getDescription());
+            vh.mDescriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
+                    View.GONE : View.VISIBLE);
+        }
+        // Clients might want the check mark view to be gone entirely, in which case, ignore it.
+        if (vh.mCheckmarkView != null && vh.mCheckmarkView.getVisibility() != View.GONE) {
+            vh.mCheckmarkView.setVisibility(action.isChecked() ? View.VISIBLE : View.INVISIBLE);
+        }
+
+        if (vh.mContentView != null) {
+            ViewGroup.LayoutParams contentLp = vh.mContentView.getLayoutParams();
+            if (setIcon(vh.mIconView, action)) {
+                contentLp.width = mContentWidth;
+            } else {
+                contentLp.width = mContentWidthNoIcon;
+            }
+            vh.mContentView.setLayoutParams(contentLp);
+        }
+
+        if (vh.mChevronView != null) {
+            vh.mChevronView.setVisibility(action.hasNext() ? View.VISIBLE : View.INVISIBLE);
+            vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
+                    mDisabledChevronAlpha);
+        }
+
+        if (action.hasMultilineDescription()) {
+            if (vh.mTitleView != null) {
+                vh.mTitleView.setMaxLines(mTitleMaxLines);
+                if (vh.mDescriptionView != null) {
+                    vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(vh.view.getContext(),
+                            vh.mTitleView));
+                }
+            }
+        } else {
+            if (vh.mTitleView != null) {
+                vh.mTitleView.setMaxLines(mTitleMinLines);
+            }
+            if (vh.mDescriptionView != null) {
+                vh.mDescriptionView.setMaxLines(mDescriptionMinLines);
+            }
+        }
+    }
+
+    /**
+     * Animates the view holder's view (or subviews thereof) when the action has had its focus
+     * state changed.
+     * @param vh The view holder associated with the relevant action.
+     * @param focused True if the action has become focused, false if it has lost focus.
+     */
+    public void onAnimateItemFocused(ViewHolder vh, boolean focused) {
+        // No animations for this, currently, because the animation is done on
+        // mSelectorView
+    }
+
+    /**
+     * Animates the view holder's view (or subviews thereof) when the action has had its press
+     * state changed.
+     * @param vh The view holder associated with the relevant action.
+     * @param pressed True if the action has been pressed, false if it has been unpressed.
+     */
+    public void onAnimateItemPressed(ViewHolder vh, boolean pressed) {
+        int attr = pressed ? R.attr.guidedActionPressedAnimation :
+                R.attr.guidedActionUnpressedAnimation;
+        createAnimator(vh.view, attr).start();
+    }
+
+    /**
+     * Animates the view holder's view (or subviews thereof) when the action has had its check
+     * state changed.
+     * @param vh The view holder associated with the relevant action.
+     * @param checked True if the action has become checked, false if it has become unchecked.
+     */
+    public void onAnimateItemChecked(ViewHolder vh, boolean checked) {
+        final View checkView = vh.mCheckmarkView;
+        if (checkView != null) {
+            if (checked) {
+                checkView.setVisibility(View.VISIBLE);
+                createAnimator(checkView, R.attr.guidedActionCheckedAnimation).start();
+            } else {
+                Animator animator = createAnimator(checkView, R.attr.guidedActionCheckedAnimation);
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        checkView.setVisibility(View.INVISIBLE);
+                    }
+                });
+                animator.start();
+            }
+        }
+    }
+
+    /*
+     * ==========================================
+     * FragmentAnimationProvider overrides
+     * ==========================================
+     */
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityEnter(@NonNull List<Animator> animators) {
+        animators.add(createAnimator(mMainView, R.attr.guidedActionsEntryAnimation));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onActivityExit(@NonNull List<Animator> animators) {}
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentEnter(@NonNull List<Animator> animators) {
+        animators.add(createAnimator(mActionsGridView, R.attr.guidedStepEntryAnimation));
+        animators.add(createAnimator(mSelectorView, R.attr.guidedStepEntryAnimation));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentExit(@NonNull List<Animator> animators) {
+        animators.add(createAnimator(mActionsGridView, R.attr.guidedStepExitAnimation));
+        animators.add(createAnimator(mSelectorView, R.attr.guidedStepExitAnimation));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentReenter(@NonNull List<Animator> animators) {
+        animators.add(createAnimator(mActionsGridView, R.attr.guidedStepReentryAnimation));
+        animators.add(createAnimator(mSelectorView, R.attr.guidedStepReentryAnimation));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onFragmentReturn(@NonNull List<Animator> animators) {
+        animators.add(createAnimator(mActionsGridView, R.attr.guidedStepReturnAnimation));
+        animators.add(createAnimator(mSelectorView, R.attr.guidedStepReturnAnimation));
+    }
+
+    /*
+     * ==========================================
+     * Private methods
+     * ==========================================
+     */
+
+    private void updateSelectorView(View focusedChild) {
+        // Display the selector view.
+        int height = focusedChild.getHeight();
+        LayoutParams lp = mSelectorView.getLayoutParams();
+        lp.height = height;
+        mSelectorView.setLayoutParams(lp);
+        mSelectorView.setAlpha(1f);
+    }
+
+    private float getFloat(Context ctx, TypedValue typedValue, int attrId) {
+        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
+        // Android resources don't have a native float type, so we have to use strings.
+        return Float.valueOf(ctx.getResources().getString(typedValue.resourceId));
+    }
+
+    private int getInteger(Context ctx, TypedValue typedValue, int attrId) {
+        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
+        return ctx.getResources().getInteger(typedValue.resourceId);
+    }
+
+    private int getDimension(Context ctx, TypedValue typedValue, int attrId) {
+        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
+        return ctx.getResources().getDimensionPixelSize(typedValue.resourceId);
+    }
+
+    private static Animator createAnimator(View v, int attrId) {
+        Context ctx = v.getContext();
+        TypedValue typedValue = new TypedValue();
+        ctx.getTheme().resolveAttribute(attrId, typedValue, true);
+        Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
+        animator.setTarget(v);
+        return animator;
+    }
+
+    private boolean setIcon(final ImageView iconView, GuidedAction action) {
+        Drawable icon = null;
+        if (iconView != null) {
+            Context context = iconView.getContext();
+            icon = action.getIcon();
+            if (icon != null) {
+                iconView.setImageDrawable(icon);
+                iconView.setVisibility(View.VISIBLE);
+            } else {
+                iconView.setVisibility(View.GONE);
+            }
+        }
+        return icon != null;
+    }
+
+    /**
+     * @return the max height in pixels the description can be such that the
+     *         action nicely takes up the entire screen.
+     */
+    private int getDescriptionMaxHeight(Context context, TextView title) {
+        // The 2 multiplier on the title height calculation is a
+        // conservative estimate for font padding which can not be
+        // calculated at this stage since the view hasn't been rendered yet.
+        return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight());
+    }
+
+    /**
+     * SelectorAnimator
+     * Controls animation for selected item backgrounds
+     * TODO: Move into focus animation override?
+     */
+    private static class SelectorAnimator extends RecyclerView.OnScrollListener {
+
+        private final View mSelectorView;
+        private final ViewGroup mParentView;
+        private volatile boolean mFadedOut = true;
+
+        SelectorAnimator(View selectorView, ViewGroup parentView) {
+            mSelectorView = selectorView;
+            mParentView = parentView;
+        }
+
+        // We want to fade in the selector if we've stopped scrolling on it. If
+        // we're scrolling, we want to ensure to dim the selector if we haven't
+        // already. We dim the last highlighted view so that while a user is
+        // scrolling, nothing is highlighted.
+        @Override
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+            Animator animator = null;
+            boolean fadingOut = false;
+            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                // The selector starts with a height of 0. In order to scale up from
+                // 0, we first need the set the height to 1 and scale from there.
+                View focusedChild = mParentView.getFocusedChild();
+                if (focusedChild != null) {
+                    int selectorHeight = mSelectorView.getHeight();
+                    float scaleY = (float) focusedChild.getHeight() / selectorHeight;
+                    AnimatorSet animators = (AnimatorSet)createAnimator(mSelectorView,
+                            R.attr.guidedActionsSelectorShowAnimation);
+                    if (mFadedOut) {
+                        // selector is completely faded out, so we can just scale before fading in.
+                        mSelectorView.setScaleY(scaleY);
+                        animator = animators.getChildAnimations().get(0);
+                    } else {
+                        // selector is not faded out, so we must animate the scale as we fade in.
+                        ((ObjectAnimator)animators.getChildAnimations().get(1))
+                                .setFloatValues(scaleY);
+                        animator = animators;
+                    }
+                }
+            } else {
+                animator = createAnimator(mSelectorView, R.attr.guidedActionsSelectorHideAnimation);
+                fadingOut = true;
+            }
+            if (animator != null) {
+                animator.addListener(new Listener(fadingOut));
+                animator.start();
+            }
+        }
+
+        /**
+         * Sets {@link BaseScrollAdapterFragment#mFadedOut}
+         * {@link BaseScrollAdapterFragment#mFadedOut} is true, iff
+         * {@link BaseScrollAdapterFragment#mSelectorView} has an alpha of 0
+         * (faded out). If false the view either has an alpha of 1 (visible) or
+         * is in the process of animating.
+         */
+        private class Listener implements Animator.AnimatorListener {
+            private boolean mFadingOut;
+            private boolean mCanceled;
+
+            public Listener(boolean fadingOut) {
+                mFadingOut = fadingOut;
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (!mFadingOut) {
+                    mFadedOut = false;
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!mCanceled && mFadingOut) {
+                    mFadedOut = true;
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCanceled = true;
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {
+            }
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index feb9e55..c324844 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -94,14 +94,14 @@
         @Override
         public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
             // Only when having an OnItemClickListner, we will attach the OnClickListener.
-            if (getOnItemViewClickedListener() != null) {
+            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
                 viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
                                 mRowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
-                        if (getOnItemViewClickedListener() != null) {
-                            getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
+                        if (mRowViewHolder.getOnItemViewClickedListener() != null) {
+                            mRowViewHolder.getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
                                     ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow);
                         }
                     }
@@ -111,7 +111,7 @@
 
         @Override
         public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
-            if (getOnItemViewClickedListener() != null) {
+            if (mRowViewHolder.getOnItemViewClickedListener() != null) {
                 viewHolder.mHolder.view.setOnClickListener(null);
             }
         }
@@ -343,21 +343,21 @@
                         rowViewHolder.mGridView.getChildViewHolder(view);
 
                 if (mHoverCardPresenterSelector != null) {
-                    rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
-                            ibh.mItem);
+                    rowViewHolder.mHoverCardViewSwitcher.select(
+                            rowViewHolder.mGridView, view, ibh.mItem);
                 }
-                if (getOnItemViewSelectedListener() != null) {
-                    getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem,
-                            rowViewHolder, rowViewHolder.mRow);
+                if (rowViewHolder.getOnItemViewSelectedListener() != null) {
+                    rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
+                            ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow);
                 }
             }
         } else {
             if (mHoverCardPresenterSelector != null) {
                 rowViewHolder.mHoverCardViewSwitcher.unselect();
             }
-            if (getOnItemViewSelectedListener() != null) {
-                getOnItemViewSelectedListener().onItemSelected(null, null,
-                        rowViewHolder, rowViewHolder.mRow);
+            if (rowViewHolder.getOnItemViewSelectedListener() != null) {
+                rowViewHolder.getOnItemViewSelectedListener().onItemSelected(
+                        null, null, rowViewHolder, rowViewHolder.mRow);
             }
         }
     }
@@ -431,8 +431,8 @@
         }
 
         if (selected) {
-            if (getOnItemViewSelectedListener() != null) {
-                getOnItemViewSelectedListener().onItemSelected(
+            if (holder.getOnItemViewSelectedListener() != null) {
+                holder.getOnItemViewSelectedListener().onItemSelected(
                         itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow());
             }
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
index fc7b38c..9cfe3eb 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -174,8 +174,8 @@
         public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item,
                 ControlBarPresenter.BoundData data) {
             ViewHolder vh = ((BoundData) data).mRowViewHolder;
-            if (getOnItemViewClickedListener() != null) {
-                getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
+            if (vh.getOnItemViewClickedListener() != null) {
+                vh.getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
                         vh, vh.getRow());
             }
             if (mOnActionClickedListener != null && item instanceof Action) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index cf5e36b..30e1b33 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -150,6 +150,8 @@
         float mSelectLevel = 0f; // initially unselected
         protected final ColorOverlayDimmer mColorDimmer;
         private View.OnKeyListener mOnKeyListener;
+        private OnItemViewSelectedListener mOnItemViewSelectedListener;
+        private OnItemViewClickedListener mOnItemViewClickedListener;
 
         /**
          * Constructor for ViewHolder.
@@ -241,11 +243,42 @@
         public View.OnKeyListener getOnKeyListener() {
             return mOnKeyListener;
         }
+
+        /**
+         * Sets the listener for item or row selection.  RowPresenter fires row selection
+         * event with null item.  A subclass of RowPresenter e.g. {@link ListRowPresenter} may
+         * fire a selection event with selected item.
+         */
+        public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+            mOnItemViewSelectedListener = listener;
+        }
+
+        /**
+         * Returns the listener for item or row selection.
+         */
+        public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
+            return mOnItemViewSelectedListener;
+        }
+
+        /**
+         * Sets the listener for item click event.  RowPresenter does nothing but subclass of
+         * RowPresenter may fire item click event if it has the concept of item.
+         * OnItemViewClickedListener will override {@link View.OnClickListener} that
+         * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+         */
+        public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+            mOnItemViewClickedListener = listener;
+        }
+
+        /**
+         * Returns the listener for item click event.
+         */
+        public final OnItemViewClickedListener getOnItemViewClickedListener() {
+            return mOnItemViewClickedListener;
+        }
     }
 
     private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
-    private OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
 
     boolean mSelectEffectEnabled = true;
     int mSyncActivatePolicy = SYNC_ACTIVATED_TO_EXPANDED;
@@ -419,8 +452,8 @@
      */
     protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
         if (selected) {
-            if (mOnItemViewSelectedListener != null) {
-                mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
+            if (vh.mOnItemViewSelectedListener != null) {
+                vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
             }
         }
     }
@@ -573,40 +606,6 @@
     }
 
     /**
-     * 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 setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        mOnItemViewSelectedListener = listener;
-    }
-
-    /**
-     * Get listener for item or row selection.
-     */
-    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
-        return mOnItemViewSelectedListener;
-    }
-
-    /**
-     * 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.
-     * OnItemViewClickedListener will override {@link View.OnClickListener} that
-     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general,  developer should choose one of the listeners but not both.
-     */
-    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mOnItemViewClickedListener = listener;
-    }
-
-    /**
-     * Set listener for item click event.
-     */
-    public final OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    /**
      * Freeze/Unfreeze the row, typically used when transition starts/ends.
      * This method is called by fragment, app should not call it directly.
      */
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
index f19de7a..178d59a 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -36,6 +36,7 @@
 public class GridWidgetTest extends ActivityInstrumentationTestCase2<GridActivity> {
 
     private static final boolean HUMAN_DELAY = false;
+    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
 
     protected GridActivity mActivity;
     protected Instrumentation mInstrumentation;
@@ -100,8 +101,12 @@
      */
     protected void waitForScrollIdle(Runnable verify) throws Throwable {
         Thread.sleep(100);
+        int total = 0;
         while (mGridView.getLayoutManager().isSmoothScrolling() ||
                 mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
+            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
+                throw new RuntimeException("waitForScrollIdle Timeout");
+            }
             try {
                 Thread.sleep(100);
             } catch (InterruptedException ex) {
@@ -1046,4 +1051,42 @@
         }
 
     }
+
+    public void testScrollToNoneExisting() throws Throwable {
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+        initActivity(intent);
+
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mGridView.setSelectedPositionSmooth(99);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        Thread.sleep(100);
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mGridView.requestLayout();
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+    }
+
 }
diff --git a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
index 0ae3a5c..ce6abf3 100644
--- a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
+++ b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.view.accessibility;
 
+import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
@@ -46,6 +47,22 @@
                 columnSpan, heading, selected);
     }
 
+    public static CharSequence getError(Object info) {
+        return ((AccessibilityNodeInfo) info).getError();
+    }
+
+    public static void setError(Object info, CharSequence error) {
+        ((AccessibilityNodeInfo) info).setError(error);
+    }
+
+    public static void setLabelFor(Object info, View labeled) {
+        ((AccessibilityNodeInfo) info).setLabelFor(labeled);
+    }
+
+    public static void setLabelFor(Object info, View root, int virtualDescendantId) {
+        ((AccessibilityNodeInfo) info).setLabelFor(root, virtualDescendantId);
+    }
+
     static class CollectionItemInfo {
         public static boolean isSelected(Object info) {
             return ((AccessibilityNodeInfo.CollectionItemInfo) info).isSelected();
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index f869e0a..eb6ab0d 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -31,6 +31,7 @@
 import android.support.annotation.StringRes;
 import android.support.v4.util.SimpleArrayMap;
 import android.support.v4.util.DebugUtils;
+import android.support.v4.view.LayoutInflaterCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -932,7 +933,7 @@
     public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
         LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity);
         getChildFragmentManager(); // Init if needed; use raw implementation below.
-        result.setFactory(mChildFragmentManager.getLayoutInflaterFactory());
+        LayoutInflaterCompat.setFactory(result, mChildFragmentManager.getLayoutInflaterFactory());
         return result;
     }
     
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 9bcb8b6..566ffed 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -297,7 +297,7 @@
             return super.onCreateView(name, context, attrs);
         }
 
-        final View v = mFragments.onCreateView(name, context, attrs);
+        final View v = mFragments.onCreateView(null, name, context, attrs);
         if (v == null) {
             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 f15bb79..6d41d45 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -30,11 +30,11 @@
 import android.support.annotation.StringRes;
 import android.support.v4.util.DebugUtils;
 import android.support.v4.util.LogWriter;
+import android.support.v4.view.LayoutInflaterFactory;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.LayoutInflater;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -410,7 +410,7 @@
 /**
  * Container for fragments associated with an activity.
  */
-final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
     static boolean DEBUG = false;
     static final String TAG = "FragmentManager";
     
@@ -2118,7 +2118,7 @@
     }
 
     @Override
-    public View onCreateView(String name, Context context, AttributeSet attrs) {
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
         if (!"fragment".equals(name)) {
             return null;
         }
@@ -2138,7 +2138,6 @@
             return null;
         }
 
-        View parent = null; // NOTE: no way to get parent pre-Honeycomb.
         int containerId = parent != null ? parent.getId() : 0;
         if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
             throw new IllegalArgumentException(attrs.getPositionDescription()
@@ -2210,7 +2209,7 @@
         return fragment.mView;
     }
 
-    LayoutInflater.Factory getLayoutInflaterFactory() {
+    LayoutInflaterFactory getLayoutInflaterFactory() {
         return this;
     }
 
diff --git a/v4/java/android/support/v4/graphics/ColorUtils.java b/v4/java/android/support/v4/graphics/ColorUtils.java
index 91c61af..f2d56da 100644
--- a/v4/java/android/support/v4/graphics/ColorUtils.java
+++ b/v4/java/android/support/v4/graphics/ColorUtils.java
@@ -32,18 +32,27 @@
      * Composite two potentially translucent colors over each other and returns the result.
      */
     public static int compositeColors(int foreground, int background) {
-        final float alpha1 = Color.alpha(foreground) / 255f;
-        final float alpha2 = Color.alpha(background) / 255f;
+        int bgAlpha = Color.alpha(background);
+        int fgAlpha = Color.alpha(foreground);
+        int a = compositeAlpha(fgAlpha, bgAlpha);
 
-        float a = (alpha1 + alpha2) * (1f - alpha1);
-        float r = (Color.red(foreground) * alpha1)
-                + (Color.red(background) * alpha2 * (1f - alpha1));
-        float g = (Color.green(foreground) * alpha1)
-                + (Color.green(background) * alpha2 * (1f - alpha1));
-        float b = (Color.blue(foreground) * alpha1)
-                + (Color.blue(background) * alpha2 * (1f - alpha1));
+        int r = compositeComponent(Color.red(foreground), fgAlpha,
+                Color.red(background), bgAlpha, a);
+        int g = compositeComponent(Color.green(foreground), fgAlpha,
+                Color.green(background), bgAlpha, a);
+        int b = compositeComponent(Color.blue(foreground), fgAlpha,
+                Color.blue(background), bgAlpha, a);
 
-        return Color.argb((int) a, (int) r, (int) g, (int) b);
+        return Color.argb(a, r, g, b);
+    }
+
+    private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
+        return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
+    }
+
+    private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) {
+        if (a == 0) return 0;
+        return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF);
     }
 
     /**
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 055598f..1b239a7 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -279,6 +279,12 @@
         public AccessibilityNodeInfoCompat getTraversalAfter(Object info);
         public void setTraversalAfter(Object info, View view);
         public void setTraversalAfter(Object info, View root, int virtualDescendantId);
+        public void setContentInvalid(Object info, boolean contentInvalid);
+        public boolean isContentInvalid(Object info);
+        public void setError(Object info, CharSequence error);
+        public CharSequence getError(Object info);
+        public void setLabelFor(Object info, View labeled);
+        public void setLabelFor(Object info, View root, int virtualDescendantId);
     }
 
     static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@@ -732,6 +738,32 @@
         @Override
         public void setTraversalAfter(Object info, View root, int virtualDescendantId) {
         }
+
+        @Override
+        public void setContentInvalid(Object info, boolean contentInvalid) {
+        }
+
+        @Override
+        public boolean isContentInvalid(Object info) {
+            return false;
+        }
+
+        @Override
+        public void setError(Object info, CharSequence error) {
+        }
+
+        @Override
+        public CharSequence getError(Object info) {
+            return null;
+        }
+
+        @Override
+        public void setLabelFor(Object info, View labeled) {
+        }
+
+        @Override
+        public void setLabelFor(Object info, View root, int virtualDescendantId) {
+        }
     }
 
     static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -1140,6 +1172,16 @@
         public void setCollectionItemInfo(Object info, Object collectionItemInfo) {
             AccessibilityNodeInfoCompatKitKat.setCollectionItemInfo(info, collectionItemInfo);
         }
+
+        @Override
+        public void setContentInvalid(Object info, boolean contentInvalid) {
+            AccessibilityNodeInfoCompatKitKat.setContentInvalid(info, contentInvalid);
+        }
+
+        @Override
+        public boolean isContentInvalid(Object info) {
+            return AccessibilityNodeInfoCompatKitKat.isContentInvalid(info);
+        }
     }
 
     static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
@@ -1186,6 +1228,26 @@
         public boolean isCollectionItemSelected(Object info) {
             return AccessibilityNodeInfoCompatApi21.CollectionItemInfo.isSelected(info);
         }
+
+        @Override
+        public CharSequence getError(Object info) {
+            return AccessibilityNodeInfoCompatApi21.getError(info);
+        }
+
+        @Override
+        public void setError(Object info, CharSequence error) {
+            AccessibilityNodeInfoCompatApi21.setError(info, error);
+        }
+
+        @Override
+        public void setLabelFor(Object info, View labeled) {
+            AccessibilityNodeInfoCompatApi21.setLabelFor(info, labeled);
+        }
+
+        @Override
+        public void setLabelFor(Object info, View root, int virtualDescendantId) {
+            AccessibilityNodeInfoCompatApi21.setLabelFor(info, root, virtualDescendantId);
+        }
     }
 
     static class AccessibilityNodeInfoApi22Impl extends AccessibilityNodeInfoApi21Impl {
@@ -2531,6 +2593,83 @@
         }
     }
 
+    /**
+     * Sets if the content of this node is invalid. For example,
+     * a date is not well-formed.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param contentInvalid If the node content is invalid.
+     */
+    public void setContentInvalid(boolean contentInvalid) {
+        IMPL.setContentInvalid(mInfo, contentInvalid);
+    }
+
+    /**
+     * Gets if the content of this node is invalid. For example,
+     * a date is not well-formed.
+     *
+     * @return If the node content is invalid.
+     */
+    public boolean isContentInvalid() {
+        return IMPL.isContentInvalid(mInfo);
+    }
+
+    /**
+     * Sets the error text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param error The error text.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setError(CharSequence error) {
+        IMPL.setError(mInfo, error);
+    }
+
+    /**
+     * Gets the error text of this node.
+     *
+     * @return The error text.
+     */
+    public CharSequence getError() {
+        return IMPL.getError(mInfo);
+    }
+
+    /**
+     * Sets the view for which the view represented by this info serves as a
+     * label for accessibility purposes.
+     *
+     * @param labeled The view for which this info serves as a label.
+     */
+    public void setLabelFor(View labeled) {
+        IMPL.setLabelFor(mInfo, labeled);
+    }
+
+    /**
+     * Sets the view for which the view represented by this info serves as a
+     * label for accessibility purposes. If <code>virtualDescendantId</code>
+     * is {@link View#NO_ID} the root is set as the labeled.
+     * <p>
+     * A virtual descendant is an imaginary View that is reported as a part of the view
+     * hierarchy for accessibility purposes. This enables custom views that draw complex
+     * content to report themselves as a tree of virtual views, thus conveying their
+     * logical structure.
+     * </p>
+     *
+     * @param root The root whose virtual descendant serves as a label.
+     * @param virtualDescendantId The id of the virtual descendant.
+     */
+    public void setLabelFor(View root, int virtualDescendantId) {
+        IMPL.setLabelFor(mInfo, root, virtualDescendantId);
+    }
 
     @Override
     public int hashCode() {
diff --git a/v4/java/android/support/v4/widget/ViewDragHelper.java b/v4/java/android/support/v4/widget/ViewDragHelper.java
index c6bebd3..3f4e571 100644
--- a/v4/java/android/support/v4/widget/ViewDragHelper.java
+++ b/v4/java/android/support/v4/widget/ViewDragHelper.java
@@ -1003,6 +1003,8 @@
             }
 
             case MotionEvent.ACTION_MOVE: {
+                if (mInitialMotionX == null || mInitialMotionY == null) break;
+
                 // First to cross a touch slop over a draggable view wins. Also report edge drags.
                 final int pointerCount = MotionEventCompat.getPointerCount(ev);
                 for (int i = 0; i < pointerCount; i++) {
diff --git a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
index 16af9bd..32fb86f 100644
--- a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
@@ -63,6 +63,14 @@
                 columnSpan, heading);
     }
 
+    public static void setContentInvalid(Object info, boolean contentInvalid) {
+        ((AccessibilityNodeInfo) info).setContentInvalid(contentInvalid);
+    }
+
+    public static boolean isContentInvalid(Object info) {
+        return ((AccessibilityNodeInfo) info).isContentInvalid();
+    }
+
     static class CollectionInfo {
         static int getColumnCount(Object info) {
             return ((AccessibilityNodeInfo.CollectionInfo) info).getColumnCount();
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
index e041005..777f5a0 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.LayerDrawable;
 import android.os.Build;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.ColorUtils;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.util.LruCache;
 import android.support.v7.appcompat.R;
@@ -440,22 +441,25 @@
         final int[] colors = new int[4];
         int i = 0;
 
+        final int colorButtonNormal = getThemeAttrColor(context, R.attr.colorButtonNormal);
+        final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight);
+
         // Disabled state
         states[i] = ThemeUtils.DISABLED_STATE_SET;
         colors[i] = getDisabledThemeAttrColor(context, R.attr.colorButtonNormal);
         i++;
 
         states[i] = ThemeUtils.PRESSED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlHighlight);
+        colors[i] = ColorUtils.compositeColors(colorControlHighlight, colorButtonNormal);
         i++;
 
         states[i] = ThemeUtils.FOCUSED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlHighlight);
+        colors[i] = ColorUtils.compositeColors(colorControlHighlight, colorButtonNormal);
         i++;
 
         // Default enabled state
         states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorButtonNormal);
+        colors[i] = colorButtonNormal;
         i++;
 
         return new ColorStateList(states, colors);
@@ -515,6 +519,12 @@
         } else {
             background.clearColorFilter();
         }
+
+        if (Build.VERSION.SDK_INT <= 10) {
+            // On Gingerbread, GradientDrawable does not invalidate itself when it's ColorFilter
+            // has changed, so we need to force an invalidation
+            view.invalidate();
+        }
     }
 
     private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index ad5c7f8..f64dee4 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -42,7 +42,10 @@
      */
     static final int MAIN_DIR_SPEC =
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-
+    /**
+     * Span size have been changed but we've not done a new layout calculation.
+     */
+    boolean mPendingSpanCountChange = false;
     int mSpanCount = DEFAULT_SPAN_COUNT;
     /**
      * The size of each span
@@ -153,6 +156,9 @@
             validateChildOrder();
         }
         clearPreLayoutSpanMappingCache();
+        if (!state.isPreLayout()) {
+            mPendingSpanCountChange = false;
+        }
     }
 
     private void clearPreLayoutSpanMappingCache() {
@@ -553,6 +559,7 @@
         if (spanCount == mSpanCount) {
             return;
         }
+        mPendingSpanCountChange = true;
         if (spanCount < 1) {
             throw new IllegalArgumentException("Span count should be at least 1. Provided "
                     + spanCount);
@@ -732,7 +739,7 @@
 
     @Override
     public boolean supportsPredictiveItemAnimations() {
-        return mPendingSavedState == null;
+        return mPendingSavedState == null && !mPendingSpanCountChange;
     }
 
     /**
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 058a565..1702a32 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -895,7 +895,8 @@
             return;
         }
         if (DEBUG) {
-            Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, new Exception());
+            Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
+                    new Exception());
         }
         mScrollState = state;
         if (state != SCROLL_STATE_SETTLING) {
@@ -1570,7 +1571,7 @@
 
     @Override
     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
-        if (!mLayout.onAddFocusables(this, views, direction, focusableMode)) {
+        if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) {
             super.addFocusables(views, direction, focusableMode);
         }
     }
@@ -3233,6 +3234,24 @@
         }
     }
 
+    /**
+     * Returns whether there are pending adapter updates which are not yet applied to the layout.
+     * <p>
+     * If this method returns <code>true</code>, it means that what user is currently seeing may not
+     * reflect them adapter contents (depending on what has changed).
+     * You may use this information to defer or cancel some operations.
+     * <p>
+     * This method returns true if RecyclerView has not yet calculated the first layout after it is
+     * attached to the Window or the Adapter has been replaced.
+     *
+     * @return True if there are some adapter updates which are not yet reflected to layout or false
+     * if layout is up to date.
+     */
+    public boolean hasPendingAdapterUpdates() {
+        return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout
+                || mAdapterHelper.hasPendingUpdates();
+    }
+
     private class ViewFlinger implements Runnable {
         private int mLastFlingX;
         private int mLastFlingY;
@@ -6402,16 +6421,21 @@
             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.
+            // of a large rect into view. If we decide to bring in end because start is already
+            // visible, limit the scroll such that start won't go out of bounds.
             final int dx;
-            if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
-                dx = offScreenRight != 0 ? offScreenRight : offScreenLeft;
+            if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
+                dx = offScreenRight != 0 ? offScreenRight
+                        : Math.max(offScreenLeft, childRight - parentRight);
             } else {
-                dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight;
+                dx = offScreenLeft != 0 ? offScreenLeft
+                        : Math.min(childLeft - parentLeft, offScreenRight);
             }
 
-            // Favor bringing the top into view over the bottom
-            final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;
+            // Favor bringing the top into view over the bottom. If top is already visible and
+            // we should scroll to make bottom visible, make sure top does not go out of bounds.
+            final int dy = offScreenTop != 0 ? offScreenTop
+                    : Math.min(childTop - parentTop, offScreenBottom);
 
             if (dx != 0 || dy != 0) {
                 if (immediate) {
@@ -6782,7 +6806,6 @@
          */
         public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
                 AccessibilityNodeInfoCompat info) {
-            info.setClassName(RecyclerView.class.getName());
             if (ViewCompat.canScrollVertically(mRecyclerView, -1) ||
                     ViewCompat.canScrollHorizontally(mRecyclerView, -1)) {
                 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
index ed7dfd6..3fe9abc 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
@@ -35,12 +35,16 @@
         mRecyclerView = recyclerView;
     }
 
+    private boolean shouldIgnore() {
+        return mRecyclerView.hasPendingAdapterUpdates();
+    }
+
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         if (super.performAccessibilityAction(host, action, args)) {
             return true;
         }
-        if (mRecyclerView.getLayoutManager() != null) {
+        if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
             return mRecyclerView.getLayoutManager().performAccessibilityAction(action, args);
         }
 
@@ -51,7 +55,7 @@
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
         info.setClassName(RecyclerView.class.getName());
-        if (mRecyclerView.getLayoutManager() != null) {
+        if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
             mRecyclerView.getLayoutManager().onInitializeAccessibilityNodeInfo(info);
         }
     }
@@ -60,7 +64,7 @@
     public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(host, event);
         event.setClassName(RecyclerView.class.getName());
-        if (host instanceof RecyclerView) {
+        if (host instanceof RecyclerView && !shouldIgnore()) {
             RecyclerView rv = (RecyclerView) host;
             if (rv.getLayoutManager() != null) {
                 rv.getLayoutManager().onInitializeAccessibilityEvent(event);
@@ -76,7 +80,7 @@
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
-            if (mRecyclerView.getLayoutManager() != null) {
+            if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
                 mRecyclerView.getLayoutManager().
                         onInitializeAccessibilityNodeInfoForItem(host, info);
             }
@@ -87,7 +91,7 @@
             if (super.performAccessibilityAction(host, action, args)) {
                 return true;
             }
-            if (mRecyclerView.getLayoutManager() != null) {
+            if (!shouldIgnore() && mRecyclerView.getLayoutManager() != null) {
                 return mRecyclerView.getLayoutManager().
                         performAccessibilityActionForItem(host, action, args);
             }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index f58bce2..0d7a685 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -33,6 +33,8 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 abstract public class BaseRecyclerViewInstrumentationTest extends
         ActivityInstrumentationTestCase2<TestActivity> {
@@ -338,7 +340,26 @@
             return super.toString() + " item:" + mBoundItem;
         }
     }
+    class DumbLayoutManager extends TestLayoutManager {
+        ReentrantLock mLayoutLock = new ReentrantLock();
+        public void blockLayout() {
+            mLayoutLock.lock();
+        }
 
+        public void unblockLayout() {
+            mLayoutLock.unlock();
+        }
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            mLayoutLock.lock();
+            detachAndScrapAttachedViews(recycler);
+            layoutRange(recycler, 0, state.getItemCount());
+            if (layoutLatch != null) {
+                layoutLatch.countDown();
+            }
+            mLayoutLock.unlock();
+        }
+    }
     class TestLayoutManager extends RecyclerView.LayoutManager {
 
         CountDownLatch layoutLatch;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index be3557b..fa31494 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -528,6 +528,26 @@
         }
     }
 
+    public void testSpanSizeChange() throws Throwable {
+        final RecyclerView rv = setupBasic(new Config(3, 100));
+        waitForFirstLayout(rv);
+        assertTrue(mGlm.supportsPredictiveItemAnimations());
+        mGlm.expectLayout(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGlm.setSpanCount(5);
+                assertFalse(mGlm.supportsPredictiveItemAnimations());
+            }
+        });
+        checkForMainThreadException();
+        mGlm.waitForLayout(2);
+        mGlm.expectLayout(2);
+        mAdapter.deleteAndNotify(3, 2);
+        mGlm.waitForLayout(2);
+        assertTrue(mGlm.supportsPredictiveItemAnimations());
+    }
+
     public void testCacheSpanIndices() throws Throwable {
         final RecyclerView rv = setupBasic(new Config(3, 100));
         mGlm.mSpanSizeLookup.setSpanIndexCacheEnabled(true);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
index f08b3c0..42ad90a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
@@ -26,6 +26,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class RecyclerViewAccessibilityTest extends BaseRecyclerViewInstrumentationTest {
+
+    public RecyclerViewAccessibilityTest() {
+        super(false);
+    }
+
     public void testOnInitializeAccessibilityNodeInfo() throws Throwable {
         for (boolean vBefore : new boolean[]{true, false}) {
             for (boolean vAfter : new boolean[]{true, false}) {
@@ -39,6 +44,7 @@
             }
         }
     }
+
     public void onInitializeAccessibilityNodeInfoTest(final boolean verticalScrollBefore,
             final boolean horizontalScrollBefore, final boolean verticalScrollAfter,
             final boolean horizontalScrollAfter) throws Throwable {
@@ -200,15 +206,58 @@
         assertEquals(verticalScrollAfter, vScrolledFwd.get());
     }
 
-    void performAccessibilityAction(final AccessibilityDelegateCompat delegate,
-            final RecyclerView recyclerView,  final int action) throws Throwable {
+    public void testIgnoreAccessibilityIfAdapterHasChanged() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity()) {
+            //@Override
+            public boolean canScrollHorizontally(int direction) {
+                return true;
+            }
+
+            //@Override
+            public boolean canScrollVertically(int direction) {
+                return true;
+            }
+        };
+        final DumbLayoutManager layoutManager = new DumbLayoutManager();
+        final TestAdapter adapter = new TestAdapter(10);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(layoutManager);
+        layoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        layoutManager.waitForLayout(1);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = recyclerView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
-                delegate.performAccessibilityAction(recyclerView, action, null);
+                delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        final AccessibilityNodeInfoCompat info2 = AccessibilityNodeInfoCompat.obtain();
+        layoutManager.blockLayout();
+        layoutManager.expectLayouts(1);
+        adapter.deleteAndNotify(1, 1);
+        // we can run this here since we blocked layout.
+        delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
+        layoutManager.unblockLayout();
+        assertFalse("info should not be filled if data is out of date", info2.isScrollable());
+        layoutManager.waitForLayout(1);
+    }
+
+    boolean performAccessibilityAction(final AccessibilityDelegateCompat delegate,
+            final RecyclerView recyclerView,  final int action) throws Throwable {
+        final boolean[] result = new boolean[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = delegate.performAccessibilityAction(recyclerView, action, null);
             }
         });
         getInstrumentation().waitForIdleSync();
         Thread.sleep(250);
+        return result[0];
     }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 178225d..9384450 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -17,9 +17,9 @@
 
 package android.support.v7.widget;
 
+import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Debug;
 import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
 import android.test.TouchUtils;
@@ -30,6 +30,7 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -304,6 +305,89 @@
         return true;
     }
 
+    private void assertPendingUpdatesAndLayout(TestLayoutManager testLayoutManager,
+            final Runnable runnable) throws Throwable {
+        testLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                runnable.run();
+                assertTrue(mRecyclerView.hasPendingAdapterUpdates());
+            }
+        });
+        testLayoutManager.waitForLayout(1);
+        assertFalse(mRecyclerView.hasPendingAdapterUpdates());
+    }
+
+    private void setupBasic(RecyclerView recyclerView, TestLayoutManager tlm,
+            TestAdapter adapter, boolean waitForFirstLayout) throws Throwable {
+        recyclerView.setLayoutManager(tlm);
+        recyclerView.setAdapter(adapter);
+        if (waitForFirstLayout) {
+            tlm.expectLayouts(1);
+            setRecyclerView(recyclerView);
+            tlm.waitForLayout(1);
+        } else {
+            setRecyclerView(recyclerView);
+        }
+    }
+
+    public void testHasPendingUpdatesBeforeFirstLayout() throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        TestLayoutManager layoutManager = new DumbLayoutManager();
+        TestAdapter testAdapter = new TestAdapter(10);
+        setupBasic(recyclerView, layoutManager, testAdapter, false);
+        assertTrue(mRecyclerView.hasPendingAdapterUpdates());
+    }
+
+    public void testNoPendingUpdatesAfterLayout() throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        TestLayoutManager layoutManager = new DumbLayoutManager();
+        TestAdapter testAdapter = new TestAdapter(10);
+        setupBasic(recyclerView, layoutManager, testAdapter, true);
+        assertFalse(mRecyclerView.hasPendingAdapterUpdates());
+    }
+
+    public void testHasPendingUpdatesWhenAdapterIsChanged() throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        TestLayoutManager layoutManager = new DumbLayoutManager();
+        final TestAdapter testAdapter = new TestAdapter(10);
+        setupBasic(recyclerView, layoutManager, testAdapter, false);
+        assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+            @Override
+            public void run() {
+                testAdapter.notifyItemRemoved(1);
+            }
+        });
+        assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+            @Override
+            public void run() {
+                testAdapter.notifyItemInserted(2);
+            }
+        });
+
+        assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+            @Override
+            public void run() {
+                testAdapter.notifyItemMoved(2, 3);
+            }
+        });
+
+        assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+            @Override
+            public void run() {
+                testAdapter.notifyItemChanged(2);
+            }
+        });
+
+        assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+            @Override
+            public void run() {
+                testAdapter.notifyDataSetChanged();
+            }
+        });
+    }
+
     public void testTransientStateRecycleViaAdapter() throws Throwable {
         transientStateRecycleTest(true, false);
     }
@@ -2390,6 +2474,141 @@
         checkForMainThreadException();
     }
 
+    public void testFocusBigViewOnTop() throws Throwable {
+        focusTooBigViewTest(Gravity.TOP);
+    }
+
+    public void testFocusBigViewOnLeft() throws Throwable {
+        focusTooBigViewTest(Gravity.LEFT);
+    }
+
+    public void testFocusBigViewOnRight() throws Throwable {
+        focusTooBigViewTest(Gravity.RIGHT);
+    }
+
+    public void testFocusBigViewOnBottom() throws Throwable {
+        focusTooBigViewTest(Gravity.BOTTOM);
+    }
+
+    public void testFocusBigViewOnLeftRTL() throws Throwable {
+        focusTooBigViewTest(Gravity.LEFT, true);
+        assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
+                mRecyclerView.getLayoutManager().getLayoutDirection());
+    }
+
+    public void testFocusBigViewOnRightRTL() throws Throwable {
+        focusTooBigViewTest(Gravity.RIGHT, true);
+        assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
+                mRecyclerView.getLayoutManager().getLayoutDirection());
+    }
+
+    public void focusTooBigViewTest(final int gravity) throws Throwable {
+        focusTooBigViewTest(gravity, false);
+    }
+    public void focusTooBigViewTest(final int gravity, final boolean rtl) throws Throwable {
+        RecyclerView rv = new RecyclerView(getActivity());
+        if (rtl) {
+            ViewCompat.setLayoutDirection(rv, ViewCompat.LAYOUT_DIRECTION_RTL);
+        }
+        final AtomicInteger vScrollDist = new AtomicInteger(0);
+        final AtomicInteger hScrollDist = new AtomicInteger(0);
+        final AtomicInteger vDesiredDist = new AtomicInteger(0);
+        final AtomicInteger hDesiredDist = new AtomicInteger(0);
+        TestLayoutManager tlm = new TestLayoutManager() {
+
+            @Override
+            public int getLayoutDirection() {
+                return rtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                final View view = recycler.getViewForPosition(0);
+                addView(view);
+                int left = 0, top = 0;
+                view.setBackgroundColor(Color.rgb(0, 0, 255));
+                switch (gravity) {
+                    case Gravity.LEFT:
+                    case Gravity.RIGHT:
+                        view.measure(
+                                View.MeasureSpec.makeMeasureSpec((int) (getWidth() * 1.5),
+                                        View.MeasureSpec.EXACTLY),
+                                View.MeasureSpec.makeMeasureSpec((int) (getHeight() * .9),
+                                        View.MeasureSpec.AT_MOST));
+                        left = gravity == Gravity.LEFT ? getWidth() - view.getMeasuredWidth() - 80
+                                : 90;
+                        top = 0;
+                        if (ViewCompat.LAYOUT_DIRECTION_RTL == getLayoutDirection()) {
+                            hDesiredDist.set((left + view.getMeasuredWidth()) - getWidth());
+                        } else {
+                            hDesiredDist.set(left);
+                        }
+                        break;
+                    case Gravity.TOP:
+                    case Gravity.BOTTOM:
+                        view.measure(
+                                View.MeasureSpec.makeMeasureSpec((int) (getWidth() * .9),
+                                        View.MeasureSpec.AT_MOST),
+                                View.MeasureSpec.makeMeasureSpec((int) (getHeight() * 1.5),
+                                        View.MeasureSpec.EXACTLY));
+                        top = gravity == Gravity.TOP ? getHeight() - view.getMeasuredHeight() -
+                                80 : 90;
+                        left = 0;
+                        vDesiredDist.set(top);
+                        break;
+                }
+
+                view.layout(left, top, left + view.getMeasuredWidth(),
+                        top + view.getMeasuredHeight());
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public boolean canScrollHorizontally() {
+                return super.canScrollHorizontally();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                vScrollDist.addAndGet(dy);
+                getChildAt(0).offsetTopAndBottom(-dy);
+                return dy;
+            }
+
+            @Override
+            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                hScrollDist.addAndGet(dx);
+                getChildAt(0).offsetLeftAndRight(-dx);
+                return dx;
+            }
+        };
+        TestAdapter adapter = new TestAdapter(10);
+        rv.setAdapter(adapter);
+        rv.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(2);
+        View view = rv.getChildAt(0);
+        requestFocus(view);
+        Thread.sleep(1000);
+        assertEquals(vDesiredDist.get(), vScrollDist.get());
+        assertEquals(hDesiredDist.get(), hScrollDist.get());
+        assertEquals(mRecyclerView.getPaddingTop(), view.getTop());
+        if (rtl) {
+            assertEquals(mRecyclerView.getWidth() - mRecyclerView.getPaddingRight(), view.getRight());
+        } else {
+            assertEquals(mRecyclerView.getPaddingLeft(), view.getLeft());
+        }
+    }
+
     public void testFocusRectOnScreenWithDecorOffsets() throws Throwable {
         focusRectOnScreenTest(true);
     }