am 216d08fd: Merge "[RenderScript] AutoPadding & Unpadding for Vec3 Elements during copyTo & copyFrom."

* commit '216d08fd889b7e8fcc17758a5779995c13a45a16':
  [RenderScript] AutoPadding & Unpadding for Vec3 Elements during copyTo & copyFrom.
diff --git a/build.gradle b/build.gradle
index 79896f5..25e72a4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,10 +9,11 @@
     }
 }
 
-ext.supportVersion = '21.0.0'
-ext.extraVersion = 7
+ext.supportVersion = '21.0.3'
+ext.extraVersion = 10
 ext.supportRepoOut = ''
 ext.buildToolsVersion = '19.0.3'
+ext.buildNumber = Integer.toString(ext.extraVersion)
 
 /*
  * With the build server you are given two env variables.
@@ -24,6 +25,9 @@
 if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
     buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile()
     project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
+
+    // the build server does not pass the build number so we infer it from the last folder of the dist path.
+    ext.buildNumber = project.ext.distDir.getName()
 } else {
     buildDir = file('../../out/host/gradle/frameworks/support/build')
     project.ext.distDir = file('../../out/dist')
@@ -46,7 +50,7 @@
     from project.ext.supportRepoOut
     destinationDir project.ext.distDir
     into 'm2repository'
-    baseName = String.format("android_m2repository_r%02d", project.ext.extraVersion)
+    baseName = String.format("sdk-repo-linux-m2repository-%s", project.ext.buildNumber)
 }
 createArchive.dependsOn createRepository
 
@@ -85,7 +89,7 @@
     <sdk:name-display>Local Maven repository for Support Libraries</sdk:name-display>\n\
     <sdk:path>m2repository</sdk:path>\n\
     <sdk:archives>\n\
-      <sdk:archive os=\"any\" arch=\"any\">\n\
+      <sdk:archive>\n\
        <sdk:size>${size}</sdk:size>\n\
        <sdk:checksum type=\"sha1\">${sha1}</sdk:checksum>\n\
        <sdk:url>${repoArchiveName}</sdk:url>\n\
diff --git a/design/Android.mk b/design/Android.mk
new file mode 100644
index 0000000..d3eb6eb
--- /dev/null
+++ b/design/Android.mk
@@ -0,0 +1,78 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the resources using the current SDK version.
+# We do this here because the final static library must be compiled with an older
+# SDK version than the resources.  The resources library and the R class that it
+# contains will not be linked into the final static library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design-res
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
+    frameworks/support/v7/appcompat/res
+LOCAL_AAPT_FLAGS := \
+    --auto-add-overlay \
+    --extra-packages android.support.v7.appcompat
+LOCAL_JAR_EXCLUDE_FILES := none
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library to resolve cyclic dependencies between src and the platform dependent
+# implementations
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design-base
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under, base)
+LOCAL_JAVA_LIBRARIES := android-support-design-res \
+    android-support-v4 \
+    android-support-v7-appcompat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of Eclair MR1 APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design-eclair-mr1
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-base
+LOCAL_JAVA_LIBRARIES := android-support-design-res \
+    android-support-v4 \
+    android-support-v7-appcompat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of Lollipop APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design-lollipop
+LOCAL_SDK_VERSION := 21
+LOCAL_SRC_FILES := $(call all-java-files-under, lollipop)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-eclair-mr1
+LOCAL_JAVA_LIBRARIES := android-support-design-res \
+    android-support-v4 \
+    android-support-v7-appcompat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-design
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-lollipop
+LOCAL_JAVA_LIBRARIES := android-support-design-res \
+    android-support-v4 \
+    android-support-v7-appcompat
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/design/AndroidManifest.xml b/design/AndroidManifest.xml
new file mode 100644
index 0000000..963f89b
--- /dev/null
+++ b/design/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.design">
+    <uses-sdk android:minSdkVersion="7"/>
+    <application />
+</manifest>
diff --git a/design/base/android/support/design/widget/FloatingActionButtonImpl.java b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
new file mode 100644
index 0000000..4118d96
--- /dev/null
+++ b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+abstract class FloatingActionButtonImpl {
+
+    static final int[] PRESSED_ENABLED_STATE_SET = {android.R.attr.state_pressed,
+            android.R.attr.state_enabled};
+    static final int[] FOCUSED_ENABLED_STATE_SET = {android.R.attr.state_focused,
+            android.R.attr.state_enabled};
+    static final int[] EMPTY_STATE_SET = new int[0];
+
+    final View mView;
+    final ShadowViewDelegate mShadowViewDelegate;
+
+    FloatingActionButtonImpl(View view, ShadowViewDelegate shadowViewDelegate) {
+        mView = view;
+        mShadowViewDelegate = shadowViewDelegate;
+    }
+
+    abstract void setBackgroundDrawable(Drawable originalBackground, ColorStateList backgroundTint,
+            PorterDuff.Mode backgroundTintMode, int rippleColor);
+
+    abstract void setBackgroundTintList(ColorStateList tint);
+
+    abstract void setBackgroundTintMode(PorterDuff.Mode tintMode);
+
+    abstract void setRippleColor(int rippleColor);
+
+    abstract void setElevation(float elevation);
+
+    abstract void setPressedTranslationZ(float translationZ);
+
+    abstract void onDrawableStateChanged(int[] state);
+
+    abstract void jumpDrawableToCurrentState();
+
+}
diff --git a/design/base/android/support/design/widget/ShadowDrawableWrapper.java b/design/base/android/support/design/widget/ShadowDrawableWrapper.java
new file mode 100644
index 0000000..d8144d4
--- /dev/null
+++ b/design/base/android/support/design/widget/ShadowDrawableWrapper.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.support.design.R;
+import android.support.v7.graphics.drawable.DrawableWrapper;
+
+/**
+ * A {@link android.graphics.drawable.Drawable} which wraps another drawable and
+ * draws a shadow around it.
+ */
+class ShadowDrawableWrapper extends DrawableWrapper {
+    // used to calculate content padding
+    static final double COS_45 = Math.cos(Math.toRadians(45));
+
+    static final float SHADOW_MULTIPLIER = 1.5f;
+
+    static final float SHADOW_TOP_SCALE = 0.25f;
+    static final float SHADOW_HORIZ_SCALE = 0.5f;
+    static final float SHADOW_BOTTOM_SCALE = 1f;
+
+    final Paint mCornerShadowPaint;
+    final Paint mEdgeShadowPaint;
+
+    final RectF mContentBounds;
+
+    float mCornerRadius;
+
+    Path mCornerShadowPath;
+
+    // updated value with inset
+    float mMaxShadowSize;
+    // actual value set by developer
+    float mRawMaxShadowSize;
+
+    // multiplied value to account for shadow offset
+    float mShadowSize;
+    // actual value set by developer
+    float mRawShadowSize;
+
+    private boolean mDirty = true;
+
+    private final int mShadowStartColor;
+    private final int mShadowMiddleColor;
+    private final int mShadowEndColor;
+
+    private boolean mAddPaddingForCorners = true;
+
+    /**
+     * If shadow size is set to a value above max shadow, we print a warning
+     */
+    private boolean mPrintedShadowClipWarning = false;
+
+    public ShadowDrawableWrapper(Resources resources, Drawable content, float radius,
+            float shadowSize, float maxShadowSize) {
+        super(content);
+
+        mShadowStartColor = resources.getColor(R.color.shadow_start_color);
+        mShadowMiddleColor = resources.getColor(R.color.shadow_mid_color);
+        mShadowEndColor = resources.getColor(R.color.shadow_end_color);
+
+        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+        mCornerShadowPaint.setStyle(Paint.Style.FILL);
+        mCornerRadius = Math.round(radius);
+        mContentBounds = new RectF();
+        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
+        mEdgeShadowPaint.setAntiAlias(false);
+        setShadowSize(shadowSize, maxShadowSize);
+    }
+
+    /**
+     * Casts the value to an even integer.
+     */
+    private static int toEven(float value) {
+        int i = Math.round(value);
+        return (i % 2 == 1) ? i - 1 : i;
+    }
+
+    public void setAddPaddingForCorners(boolean addPaddingForCorners) {
+        mAddPaddingForCorners = addPaddingForCorners;
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        super.setAlpha(alpha);
+        mCornerShadowPaint.setAlpha(alpha);
+        mEdgeShadowPaint.setAlpha(alpha);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mDirty = true;
+    }
+
+    void setShadowSize(float shadowSize, float maxShadowSize) {
+        if (shadowSize < 0 || maxShadowSize < 0) {
+            throw new IllegalArgumentException("invalid shadow size");
+        }
+        shadowSize = toEven(shadowSize);
+        maxShadowSize = toEven(maxShadowSize);
+        if (shadowSize > maxShadowSize) {
+            shadowSize = maxShadowSize;
+            if (!mPrintedShadowClipWarning) {
+                mPrintedShadowClipWarning = true;
+            }
+        }
+        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
+            return;
+        }
+        mRawShadowSize = shadowSize;
+        mRawMaxShadowSize = maxShadowSize;
+        mShadowSize = Math.round(shadowSize * SHADOW_MULTIPLIER);
+        mMaxShadowSize = maxShadowSize;
+        mDirty = true;
+        invalidateSelf();
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
+                mAddPaddingForCorners));
+        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
+                mAddPaddingForCorners));
+        padding.set(hOffset, vOffset, hOffset, vOffset);
+        return true;
+    }
+
+    public static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
+            boolean addPaddingForCorners) {
+        if (addPaddingForCorners) {
+            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
+        } else {
+            return maxShadowSize * SHADOW_MULTIPLIER;
+        }
+    }
+
+    public static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
+            boolean addPaddingForCorners) {
+        if (addPaddingForCorners) {
+            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
+        } else {
+            return maxShadowSize;
+        }
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    public void setCornerRadius(float radius) {
+        radius = Math.round(radius);
+        if (mCornerRadius == radius) {
+            return;
+        }
+        mCornerRadius = radius;
+        mDirty = true;
+        invalidateSelf();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mDirty) {
+            buildComponents(getBounds());
+            mDirty = false;
+        }
+        drawShadow(canvas);
+
+        super.draw(canvas);
+    }
+
+    private void drawShadow(Canvas canvas) {
+        final float edgeShadowTop = -mCornerRadius - mShadowSize;
+        final float shadowOffset = mCornerRadius;
+        final boolean drawHorizontalEdges = mContentBounds.width() - 2 * shadowOffset > 0;
+        final boolean drawVerticalEdges = mContentBounds.height() - 2 * shadowOffset > 0;
+
+        final float shadowOffsetTop = mRawShadowSize - (mRawShadowSize * SHADOW_TOP_SCALE);
+        final float shadowOffsetHorizontal = mRawShadowSize - (mRawShadowSize * SHADOW_HORIZ_SCALE);
+        final float shadowOffsetBottom = mRawShadowSize - (mRawShadowSize * SHADOW_BOTTOM_SCALE);
+
+        final float shadowScaleHorizontal = shadowOffset / (shadowOffset + shadowOffsetHorizontal);
+        final float shadowScaleTop = shadowOffset / (shadowOffset + shadowOffsetTop);
+        final float shadowScaleBottom = shadowOffset / (shadowOffset + shadowOffsetBottom);
+
+        // LT
+        int saved = canvas.save();
+        canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.top + shadowOffset);
+        canvas.scale(shadowScaleHorizontal, shadowScaleTop);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawHorizontalEdges) {
+            // TE
+            canvas.scale(1f / shadowScaleHorizontal, 1f);
+            canvas.drawRect(0, edgeShadowTop,
+                    mContentBounds.width() - 2 * shadowOffset, -mCornerRadius,
+                    mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // RB
+        saved = canvas.save();
+        canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.bottom - shadowOffset);
+        canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
+        canvas.rotate(180f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawHorizontalEdges) {
+            // BE
+            canvas.scale(1f / shadowScaleHorizontal, 1f);
+            canvas.drawRect(0, edgeShadowTop,
+                    mContentBounds.width() - 2 * shadowOffset, -mCornerRadius + mShadowSize,
+                    mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // LB
+        saved = canvas.save();
+        canvas.translate(mContentBounds.left + shadowOffset, mContentBounds.bottom - shadowOffset);
+        canvas.scale(shadowScaleHorizontal, shadowScaleBottom);
+        canvas.rotate(270f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawVerticalEdges) {
+            // LE
+            canvas.scale(1f / shadowScaleBottom, 1f);
+            canvas.drawRect(0, edgeShadowTop,
+                    mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // RT
+        saved = canvas.save();
+        canvas.translate(mContentBounds.right - shadowOffset, mContentBounds.top + shadowOffset);
+        canvas.scale(shadowScaleHorizontal, shadowScaleTop);
+        canvas.rotate(90f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawVerticalEdges) {
+            // RE
+            canvas.scale(1f / shadowScaleTop, 1f);
+            canvas.drawRect(0, edgeShadowTop,
+                    mContentBounds.height() - 2 * shadowOffset, -mCornerRadius, mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+    }
+
+    private void buildShadowCorners() {
+        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
+        RectF outerBounds = new RectF(innerBounds);
+        outerBounds.inset(-mShadowSize, -mShadowSize);
+
+        if (mCornerShadowPath == null) {
+            mCornerShadowPath = new Path();
+        } else {
+            mCornerShadowPath.reset();
+        }
+        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
+        mCornerShadowPath.moveTo(-mCornerRadius, 0);
+        mCornerShadowPath.rLineTo(-mShadowSize, 0);
+        // outer arc
+        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
+        // inner arc
+        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
+        mCornerShadowPath.close();
+
+        float shadowRadius = -outerBounds.top;
+        if (shadowRadius > 0f) {
+            float startRatio = mCornerRadius / shadowRadius;
+            float midRatio = startRatio + ((1f - startRatio) / 2f);
+            mCornerShadowPaint.setShader(new RadialGradient(0, 0, shadowRadius,
+                    new int[]{0, mShadowStartColor, mShadowMiddleColor, mShadowEndColor},
+                    new float[]{0f, startRatio, midRatio, 1f},
+                    Shader.TileMode.CLAMP));
+        }
+
+        // we offset the content shadowSize/2 pixels up to make it more realistic.
+        // this is why edge shadow shader has some extra space
+        // When drawing bottom edge shadow, we use that extra space.
+        mEdgeShadowPaint.setShader(new LinearGradient(0, innerBounds.top, 0, outerBounds.top,
+                new int[]{mShadowStartColor, mShadowMiddleColor, mShadowEndColor},
+                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
+        mEdgeShadowPaint.setAntiAlias(false);
+    }
+
+    private void buildComponents(Rect bounds) {
+        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
+        // We could have different top-bottom offsets to avoid extra gap above but in that case
+        // center aligning Views inside the CardView would be problematic.
+        final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
+        mContentBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
+                bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
+
+        getWrappedDrawable().setBounds((int) mContentBounds.left, (int) mContentBounds.top,
+                (int) mContentBounds.right, (int) mContentBounds.bottom);
+
+        buildShadowCorners();
+    }
+
+    public float getCornerRadius() {
+        return mCornerRadius;
+    }
+
+    public void setShadowSize(float size) {
+        setShadowSize(size, mRawMaxShadowSize);
+    }
+
+    public void setMaxShadowSize(float size) {
+        setShadowSize(mRawShadowSize, size);
+    }
+
+    public float getShadowSize() {
+        return mRawShadowSize;
+    }
+
+    public float getMaxShadowSize() {
+        return mRawMaxShadowSize;
+    }
+
+    public float getMinWidth() {
+        final float content = 2 *
+                Math.max(mRawMaxShadowSize, mCornerRadius + mRawMaxShadowSize / 2);
+        return content + mRawMaxShadowSize * 2;
+    }
+
+    public float getMinHeight() {
+        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius
+                + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
+        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER) * 2;
+    }
+}
diff --git a/design/base/android/support/design/widget/ShadowViewDelegate.java b/design/base/android/support/design/widget/ShadowViewDelegate.java
new file mode 100644
index 0000000..9a395e6
--- /dev/null
+++ b/design/base/android/support/design/widget/ShadowViewDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.graphics.drawable.Drawable;
+
+interface ShadowViewDelegate {
+    float getRadius();
+    void setShadowPadding(int left, int top, int right, int bottom);
+    void setBackgroundDrawable(Drawable background);
+}
diff --git a/design/base/android/support/design/widget/StateListAnimator.java b/design/base/android/support/design/widget/StateListAnimator.java
new file mode 100644
index 0000000..c937b0b
--- /dev/null
+++ b/design/base/android/support/design/widget/StateListAnimator.java
@@ -0,0 +1,189 @@
+/*
+ * 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.util.StateSet;
+import android.view.View;
+import android.view.animation.Animation;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+final class StateListAnimator {
+
+    private final ArrayList<Tuple> mTuples = new ArrayList<>();
+
+    private Tuple mLastMatch = null;
+    private Animation mRunningAnimation = null;
+    private WeakReference<View> mViewRef;
+
+    private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
+        @Override
+        public void onAnimationEnd(Animation animation) {
+            if (mRunningAnimation == animation) {
+                mRunningAnimation = null;
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animation animation) {
+            // no-op
+        }
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {
+            // no-op
+        }
+    };
+
+    /**
+     * Associates the given Animation with the provided drawable state specs so that it will be run
+     * when the View's drawable state matches the specs.
+     *
+     * @param specs    The drawable state specs to match against
+     * @param animation The Animation to run when the specs match
+     */
+    public void addState(int[] specs, Animation animation) {
+        Tuple tuple = new Tuple(specs, animation);
+        animation.setAnimationListener(mAnimationListener);
+        mTuples.add(tuple);
+    }
+
+    /**
+     * Returns the current {@link Animation} which is started because of a state
+     * change.
+     *
+     * @return The currently running Animation or null if no Animation is running
+     */
+    Animation getRunningAnimation() {
+        return mRunningAnimation;
+    }
+
+
+    View getTarget() {
+        return mViewRef == null ? null : mViewRef.get();
+    }
+
+    void setTarget(View view) {
+        final View current = getTarget();
+        if (current == view) {
+            return;
+        }
+        if (current != null) {
+            clearTarget();
+        }
+        if (view != null) {
+            mViewRef = new WeakReference<>(view);
+        }
+    }
+
+    private void clearTarget() {
+        final View view = getTarget();
+        final int size = mTuples.size();
+        for (int i = 0; i < size; i++) {
+            Animation anim = mTuples.get(i).mAnimation;
+            if (view.getAnimation() == anim) {
+                view.clearAnimation();
+            }
+        }
+        mViewRef = null;
+        mLastMatch = null;
+        mRunningAnimation = null;
+    }
+
+    /**
+     * Called by View
+     */
+    void setState(int[] state) {
+        Tuple match = null;
+        final int count = mTuples.size();
+        for (int i = 0; i < count; i++) {
+            final Tuple tuple = mTuples.get(i);
+            if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
+                match = tuple;
+                break;
+            }
+        }
+        if (match == mLastMatch) {
+            return;
+        }
+        if (mLastMatch != null) {
+            cancel();
+        }
+        mLastMatch = match;
+        if (match != null) {
+            start(match);
+        }
+    }
+
+    private void start(Tuple match) {
+        mRunningAnimation = match.mAnimation;
+
+        View view = getTarget();
+        if (view != null) {
+            view.startAnimation(mRunningAnimation);
+        }
+    }
+
+    private void cancel() {
+        if (mRunningAnimation != null) {
+            final View view = getTarget();
+            if (view != null && view.getAnimation() == mRunningAnimation) {
+                view.clearAnimation();
+            }
+            mRunningAnimation = null;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    ArrayList<Tuple> getTuples() {
+        return mTuples;
+    }
+
+    /**
+     * If there is an animation running for a recent state change, ends it. <p> This causes the
+     * animation to assign the end value(s) to the View.
+     */
+    public void jumpToCurrentState() {
+        if (mRunningAnimation != null) {
+            final View view = getTarget();
+            if (view != null && view.getAnimation() == mRunningAnimation) {
+                view.clearAnimation();
+            }
+        }
+    }
+
+    static class Tuple {
+        final int[] mSpecs;
+        final Animation mAnimation;
+
+        private Tuple(int[] specs, Animation Animation) {
+            mSpecs = specs;
+            mAnimation = Animation;
+        }
+
+        int[] getSpecs() {
+            return mSpecs;
+        }
+
+        Animation getAnimation() {
+            return mAnimation;
+        }
+    }
+}
\ No newline at end of file
diff --git a/design/build.gradle b/design/build.gradle
new file mode 100644
index 0000000..5ff142d
--- /dev/null
+++ b/design/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'design'
+
+dependencies {
+    compile project(':support-v4')
+    compile project(':support-appcompat-v7')
+}
+
+android {
+    compileSdkVersion 'current'
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = ['base', 'eclair-mr1', 'lollipop', 'src']
+        main.res.srcDir 'res'
+        main.assets.srcDir 'assets'
+        main.resources.srcDir 'src'
+
+        // this moves src/instrumentTest to tests so all folders follow:
+        // tests/java, tests/res, tests/assets, ...
+        // This is a *reset* so it replaces the default paths
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/src'
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
new file mode 100644
index 0000000..175e8b9
--- /dev/null
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -0,0 +1,214 @@
+/*
+ * 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.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+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;
+
+    private float mElevation;
+    private float mPressedTranslationZ;
+    private int mAnimationDuration;
+
+    private StateListAnimator mStateListAnimator;
+
+    ShadowDrawableWrapper mShadowDrawable;
+
+    FloatingActionButtonEclairMr1(View view, ShadowViewDelegate shadowViewDelegate) {
+        super(view, shadowViewDelegate);
+
+        mAnimationDuration = view.getResources().getInteger(android.R.integer.config_shortAnimTime);
+
+        mStateListAnimator = new StateListAnimator();
+        mStateListAnimator.setTarget(view);
+
+        // Elevate with translationZ when pressed or focused
+        mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET,
+                setupAnimation(new ElevateToTranslationZAnimation()));
+        mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET,
+                setupAnimation(new ElevateToTranslationZAnimation()));
+        // Reset back to elevation by default
+        mStateListAnimator.addState(EMPTY_STATE_SET,
+                setupAnimation(new ResetElevationAnimation()));
+    }
+
+    @Override
+    void setBackgroundDrawable(Drawable originalBackground, ColorStateList backgroundTint,
+            PorterDuff.Mode backgroundTintMode, int rippleColor) {
+        // First we need to tint the original background with the tint
+        mShapeDrawable = DrawableCompat.wrap(originalBackground);
+        DrawableCompat.setTintList(mShapeDrawable, backgroundTint);
+        if (backgroundTintMode != null) {
+            DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode);
+        }
+
+        // Now we created a mask Drawable which will be used for touch feedback.
+        // As we don't know the actual outline of mShapeDrawable, we'll just guess that it's a
+        // circle
+        GradientDrawable touchFeedbackShape = new GradientDrawable();
+        touchFeedbackShape.setShape(GradientDrawable.OVAL);
+        touchFeedbackShape.setColor(Color.WHITE);
+        touchFeedbackShape.setCornerRadius(mShadowViewDelegate.getRadius());
+
+        // We'll now wrap that touch feedback mask drawable with a ColorStateList
+        mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape);
+        DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
+        DrawableCompat.setTintMode(mRippleDrawable, PorterDuff.Mode.MULTIPLY);
+
+        mShadowDrawable = new ShadowDrawableWrapper(
+                mView.getResources(),
+                new LayerDrawable(new Drawable[] {mShapeDrawable, mRippleDrawable}),
+                mShadowViewDelegate.getRadius(),
+                mElevation,
+                mElevation + mPressedTranslationZ);
+        mShadowDrawable.setAddPaddingForCorners(false);
+
+        mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable);
+
+        updatePadding();
+    }
+
+    @Override
+    void setBackgroundTintList(ColorStateList tint) {
+        DrawableCompat.setTintList(mShapeDrawable, tint);
+    }
+
+    @Override
+    void setBackgroundTintMode(PorterDuff.Mode tintMode) {
+        DrawableCompat.setTintMode(mShapeDrawable, tintMode);
+    }
+
+    @Override
+    void setRippleColor(int rippleColor) {
+        DrawableCompat.setTint(mRippleDrawable, rippleColor);
+    }
+
+    @Override
+    void setElevation(float elevation) {
+        if (mElevation != elevation && mShadowDrawable != null) {
+            mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ);
+            mElevation = elevation;
+            updatePadding();
+        }
+    }
+
+    @Override
+    void setPressedTranslationZ(float translationZ) {
+        if (mPressedTranslationZ != translationZ && mShadowDrawable != null) {
+            mPressedTranslationZ = translationZ;
+            mShadowDrawable.setMaxShadowSize(mElevation + translationZ);
+            updatePadding();
+        }
+    }
+
+    @Override
+    void onDrawableStateChanged(int[] state) {
+        mStateListAnimator.setState(state);
+    }
+
+    @Override
+    void jumpDrawableToCurrentState() {
+        mStateListAnimator.jumpToCurrentState();
+    }
+
+    private void updatePadding() {
+        Rect rect = new Rect();
+        mShadowDrawable.getPadding(rect);
+        mShadowViewDelegate.setShadowPadding(rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    private Animation setupAnimation(Animation animation) {
+        animation.setInterpolator(INTERPOLATOR);
+        animation.setDuration(mAnimationDuration);
+        return animation;
+    }
+
+    private abstract class BaseShadowAnimation extends Animation {
+        private float mShadowSizeStart;
+        private float mShadowSizeDiff;
+
+        @Override
+        public void reset() {
+            super.reset();
+
+            mShadowSizeStart = mShadowDrawable.getShadowSize();
+            mShadowSizeDiff = getTargetShadowSize() - mShadowSizeStart;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mShadowDrawable.setShadowSize(mShadowSizeStart + (mShadowSizeDiff * interpolatedTime));
+        }
+
+        /**
+         * @return the shadow size we want to animate to.
+         */
+        protected abstract float getTargetShadowSize();
+    }
+
+    private class ResetElevationAnimation extends BaseShadowAnimation {
+        @Override
+        protected float getTargetShadowSize() {
+            return mElevation;
+        }
+    }
+
+    private class ElevateToTranslationZAnimation extends BaseShadowAnimation {
+        @Override
+        protected float getTargetShadowSize() {
+            return mElevation + mPressedTranslationZ;
+        }
+    }
+
+    private static ColorStateList createColorStateList(int selectedColor) {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
+
+        states[i] = FOCUSED_ENABLED_STATE_SET;
+        colors[i] = selectedColor;
+        i++;
+
+        states[i] = PRESSED_ENABLED_STATE_SET;
+        colors[i] = selectedColor;
+        i++;
+
+        // Default enabled state
+        states[i] = new int[0];
+        colors[i] = Color.TRANSPARENT;
+        i++;
+
+        return new ColorStateList(states, colors);
+    }
+}
\ No newline at end of file
diff --git a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
new file mode 100644
index 0000000..9fdc148
--- /dev/null
+++ b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
@@ -0,0 +1,116 @@
+/*
+ * 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.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.StateListAnimator;
+import android.annotation.TargetApi;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class FloatingActionButtonLollipop extends FloatingActionButtonImpl {
+
+    private Drawable mShapeDrawable;
+    private RippleDrawable mRippleDrawable;
+
+    private final Interpolator mInterpolator;
+
+    FloatingActionButtonLollipop(View view, ShadowViewDelegate shadowViewDelegate) {
+        super(view, shadowViewDelegate);
+
+        mInterpolator = AnimationUtils.loadInterpolator(
+                mView.getContext(), android.R.interpolator.fast_out_slow_in);
+    }
+
+    @Override
+    void setBackgroundDrawable(Drawable originalBackground, ColorStateList backgroundTint,
+            PorterDuff.Mode backgroundTintMode, int rippleColor) {
+        mShapeDrawable = DrawableCompat.wrap(originalBackground);
+
+        DrawableCompat.setTintList(mShapeDrawable, backgroundTint);
+        if (backgroundTintMode != null) {
+            DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode);
+        }
+
+        mRippleDrawable = new RippleDrawable(ColorStateList.valueOf(rippleColor),
+                mShapeDrawable, null);
+
+        mShadowViewDelegate.setBackgroundDrawable(mRippleDrawable);
+        mShadowViewDelegate.setShadowPadding(0, 0, 0, 0);
+    }
+
+    @Override
+    void setBackgroundTintList(ColorStateList tint) {
+        DrawableCompat.setTintList(mShapeDrawable, tint);
+    }
+
+    @Override
+    void setBackgroundTintMode(PorterDuff.Mode tintMode) {
+        DrawableCompat.setTintMode(mShapeDrawable, tintMode);
+    }
+
+    @Override
+    void setRippleColor(int rippleColor) {
+        DrawableCompat.setTint(mRippleDrawable, rippleColor);
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+        ViewCompat.setElevation(mView, elevation);
+    }
+
+    @Override
+    void setPressedTranslationZ(float translationZ) {
+        StateListAnimator stateListAnimator = new StateListAnimator();
+
+        // Animate translationZ to our value when pressed or focused
+        stateListAnimator.addState(PRESSED_ENABLED_STATE_SET,
+                setupAnimator(ObjectAnimator.ofFloat(mView, "translationZ", translationZ)));
+        stateListAnimator.addState(FOCUSED_ENABLED_STATE_SET,
+                setupAnimator(ObjectAnimator.ofFloat(mView, "translationZ", translationZ)));
+        // Animate translationZ to 0 otherwise
+        stateListAnimator.addState(EMPTY_STATE_SET,
+                setupAnimator(ObjectAnimator.ofFloat(mView, "translationZ", 0f)));
+
+        mView.setStateListAnimator(stateListAnimator);
+    }
+
+    @Override
+    void onDrawableStateChanged(int[] state) {
+        // no-op
+    }
+
+    @Override
+    void jumpDrawableToCurrentState() {
+        // no-op
+    }
+
+    private Animator setupAnimator(Animator animator) {
+        animator.setInterpolator(mInterpolator);
+        return animator;
+    }
+}
\ No newline at end of file
diff --git a/design/res/drawable/drawer_separator_background.xml b/design/res/drawable/drawer_separator_background.xml
new file mode 100644
index 0000000..28df8f1
--- /dev/null
+++ b/design/res/drawable/drawer_separator_background.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="?android:attr/listDivider"
+    android:insetBottom="@dimen/drawer_separator_vertical_margin"
+    android:insetTop="@dimen/drawer_separator_vertical_margin" />
diff --git a/design/res/drawable/fab_background.xml b/design/res/drawable/fab_background.xml
new file mode 100644
index 0000000..43afd5c
--- /dev/null
+++ b/design/res/drawable/fab_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="oval">
+    <solid android:color="@android:color/white" />
+</shape>
\ No newline at end of file
diff --git a/design/res/layout/design_drawer_item.xml b/design/res/layout/design_drawer_item.xml
new file mode 100644
index 0000000..e5ad38c
--- /dev/null
+++ b/design/res/layout/design_drawer_item.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<android.support.design.internal.NavigationMenuItemView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/listPreferredItemHeightSmall"
+        android:paddingLeft="?attr/listPreferredItemPaddingLeft"
+        android:paddingRight="?attr/listPreferredItemPaddingRight"
+        android:drawablePadding="@dimen/drawer_icon_padding"
+        android:gravity="center_vertical|start"
+        android:maxLines="1"
+        android:textAppearance="?attr/textAppearanceListItem"
+        android:textColor="?android:attr/textColorPrimary"/>
diff --git a/design/res/layout/design_drawer_item_separator.xml b/design/res/layout/design_drawer_item_separator.xml
new file mode 100644
index 0000000..692f865
--- /dev/null
+++ b/design/res/layout/design_drawer_item_separator.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.
+-->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/drawer_separator_overall_height"
+      android:background="@drawable/drawer_separator_background"/>
diff --git a/design/res/layout/design_drawer_item_space.xml b/design/res/layout/design_drawer_item_space.xml
new file mode 100644
index 0000000..e8e7b2f
--- /dev/null
+++ b/design/res/layout/design_drawer_item_space.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.
+-->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="16dp"
+      android:visibility="invisible" />
diff --git a/design/res/layout/design_drawer_item_subheader.xml b/design/res/layout/design_drawer_item_subheader.xml
new file mode 100644
index 0000000..9a1a810
--- /dev/null
+++ b/design/res/layout/design_drawer_item_subheader.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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:layout_width="match_parent"
+          android:layout_height="?attr/listPreferredItemHeightSmall"
+          android:gravity="center_vertical|start"
+          android:maxLines="1"
+          android:paddingLeft="?attr/listPreferredItemPaddingLeft"
+          android:paddingRight="?attr/listPreferredItemPaddingRight"
+          android:textAppearance="?attr/textAppearanceListItem"
+          android:textColor="?android:textColorSecondary"/>
diff --git a/design/res/layout/design_drawer_menu.xml b/design/res/layout/design_drawer_menu.xml
new file mode 100644
index 0000000..62d6fd5
--- /dev/null
+++ b/design/res/layout/design_drawer_menu.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<android.support.design.internal.NavigationMenuView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingTop="@dimen/drawer_padding_top_default"
+        android:clipToPadding="false"
+        android:divider="@null"
+        android:listSelector="?attr/selectableItemBackground"/>
diff --git a/design/res/values-land/styles.xml b/design/res/values-land/styles.xml
new file mode 100644
index 0000000..622a5e3
--- /dev/null
+++ b/design/res/values-land/styles.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>
+
+    <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout">
+        <item name="tabGravity">center</item>
+        <item name="tabMode">fixed</item>
+    </style>
+
+</resources>
+
diff --git a/design/res/values-sw600dp/dimens.xml b/design/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..2d966e6
--- /dev/null
+++ b/design/res/values-sw600dp/dimens.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>
+
+    <dimen name="tab_min_width">160dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/design/res/values-sw600dp/styles.xml b/design/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..622a5e3
--- /dev/null
+++ b/design/res/values-sw600dp/styles.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>
+
+    <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout">
+        <item name="tabGravity">center</item>
+        <item name="tabMode">fixed</item>
+    </style>
+
+</resources>
+
diff --git a/design/res/values-v21/dimens.xml b/design/res/values-v21/dimens.xml
new file mode 100644
index 0000000..b8a4d37
--- /dev/null
+++ b/design/res/values-v21/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<resources>
+    <dimen name="drawer_padding_top_default">24dp</dimen>
+</resources>
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
new file mode 100644
index 0000000..23f618c1
--- /dev/null
+++ b/design/res/values/attrs.xml
@@ -0,0 +1,83 @@
+<?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>
+
+    <declare-styleable name="FloatingActionButton">
+        <!-- Background for the FloatingActionButton -->
+        <attr name="android:background" />
+        <attr name="backgroundTint" />
+        <attr name="backgroundTintMode" />
+
+        <!-- Ripple color for the FAB. -->
+        <attr name="rippleColor" format="color|reference" />
+        <!-- Size for the FAB. -->
+        <attr name="fabSize">
+            <enum name="normal" value="0" />
+            <enum name="mini" value="1" />
+        </attr>
+        <!-- Elevation value for the FAB -->
+        <attr name="elevation" />
+        <!-- TranslationZ value for the FAB when pressed-->
+        <attr name="pressedTranslationZ" format="dimension|reference" />
+    </declare-styleable>
+
+    <declare-styleable name="ScrimInsetsFrameLayout">
+        <attr name="insetForeground" format="color|reference" />
+    </declare-styleable>
+
+    <declare-styleable name="NavigationDrawerView">
+        <attr name="android:elevation" />
+        <attr name="android:background" />
+        <attr name="android:fitsSystemWindows" />
+        <attr name="android:maxWidth" />
+    </declare-styleable>
+
+    <declare-styleable name="TabLayout">
+        <attr name="tabIndicatorColor" format="color" />
+        <attr name="tabIndicatorHeight" format="dimension" />
+        <attr name="tabContentStart" format="dimension" />
+
+        <attr name="tabBackground" format="reference" />
+
+        <attr name="tabMode">
+            <enum name="scrollable" value="0" />
+            <enum name="fixed" value="1" />
+        </attr>
+
+        <!-- Standard gravity constant that a child supplies to its parent.
+             Defines how the child view should be positioned, on both the X and Y axes,
+             within its enclosing layout. -->
+        <attr name="tabGravity">
+            <enum name="fill" value="0"/>
+            <enum name="center" value="1"/>
+        </attr>
+
+        <attr name="tabMinWidth" format="dimension" />
+        <attr name="tabMaxWidth" format="dimension" />
+
+        <attr name="tabTextAppearance" format="reference" />
+        <attr name="tabSelectedTextColor" format="color" />
+
+        <attr name="tabPaddingStart" format="dimension" />
+        <attr name="tabPaddingTop" format="dimension" />
+        <attr name="tabPaddingEnd" format="dimension" />
+        <attr name="tabPaddingBottom" format="dimension" />
+        <attr name="tabPadding" format="dimension" />
+    </declare-styleable>
+
+</resources>
+
diff --git a/design/res/values/colors.xml b/design/res/values/colors.xml
new file mode 100644
index 0000000..fdd5127
--- /dev/null
+++ b/design/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?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>
+
+    <!-- Shadow color for the first pixels of a shadow -->
+    <color name="shadow_start_color">#44000000</color>
+    <!-- Shadow color for the middle pixels of a shadow -->
+    <color name="shadow_mid_color">#14000000</color>
+    <!-- Shadow color for the furthest pixels of a shadow -->
+    <color name="shadow_end_color">@android:color/transparent</color>
+
+</resources>
\ No newline at end of file
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
new file mode 100644
index 0000000..f7ef4a4
--- /dev/null
+++ b/design/res/values/dimens.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<resources>
+
+    <dimen name="fab_elevation">8dp</dimen>
+    <dimen name="fab_translation_z_pressed">6dp</dimen>
+    <dimen name="fab_content_size">24dp</dimen>
+    <dimen name="fab_size_normal">56dp</dimen>
+    <dimen name="fab_size_mini">40dp</dimen>
+
+    <dimen name="drawer_max_width">320dp</dimen>
+    <dimen name="drawer_elevation">12dp</dimen>
+    <dimen name="drawer_icon_padding">32dp</dimen>
+    <dimen name="drawer_icon_size">24dp</dimen>
+
+    <dimen name="tab_min_width">72dp</dimen>
+    <dimen name="tab_max_width">264dp</dimen>
+
+    <dimen name="drawer_separator_vertical_margin">8dp</dimen>
+    <!-- This needs to be drawer_separator_vertical_margin * 2 + 1 -->
+    <dimen name="drawer_separator_overall_height">17dp</dimen>
+    <dimen name="drawer_padding_top_default">0dp</dimen>
+
+</resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
new file mode 100644
index 0000000..ecfaab4
--- /dev/null
+++ b/design/res/values/styles.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<resources>
+
+    <style name="Widget.Design.FloatingActionButton" parent="android:Widget">
+        <item name="android:background">@drawable/fab_background</item>
+        <item name="backgroundTint">?attr/colorAccent</item>
+
+        <item name="fabSize">normal</item>
+        <item name="elevation">@dimen/fab_elevation</item>
+        <item name="pressedTranslationZ">@dimen/fab_translation_z_pressed</item>
+        <item name="rippleColor">?attr/colorControlHighlight</item>
+    </style>
+
+    <style name="Widget.Design.ScrimInsetsFrameLayout" parent="">
+        <item name="insetForeground">#4000</item>
+    </style>
+
+    <style name="Widget.Design.NavigationDrawerView" parent="">
+        <item name="android:elevation">@dimen/drawer_elevation</item>
+        <item name="android:background">?android:attr/windowBackground</item>
+        <item name="android:fitsSystemWindows">true</item>
+        <item name="android:maxWidth">@dimen/drawer_max_width</item>
+    </style>
+
+    <style name="Widget.Design.TabLayout" parent="Base.Widget.Design.TabLayout">
+        <item name="tabGravity">fill</item>
+        <item name="tabMode">fixed</item>
+    </style>
+
+    <style name="Base.Widget.Design.TabLayout" parent="android:Widget">
+        <item name="tabMaxWidth">@dimen/tab_max_width</item>
+        <item name="tabIndicatorColor">?attr/colorAccent</item>
+        <item name="tabIndicatorHeight">2dp</item>
+        <item name="tabPaddingStart">12dp</item>
+        <item name="tabPaddingEnd">12dp</item>
+        <item name="tabBackground">?attr/selectableItemBackground</item>
+        <item name="tabTextAppearance">@style/TextAppearance.Design.Tab</item>
+        <item name="tabSelectedTextColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="TextAppearance.Design.Tab" parent="TextAppearance.AppCompat.Button">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+        <item name="textAllCaps">true</item>
+    </style>
+
+</resources>
+
diff --git a/design/src/.readme b/design/src/.readme
new file mode 100644
index 0000000..4bcebad
--- /dev/null
+++ b/design/src/.readme
@@ -0,0 +1,2 @@
+This hidden file is there to ensure there is an src folder.
+Once we support binary library this will go away.
\ No newline at end of file
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
new file mode 100644
index 0000000..5313722
--- /dev/null
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -0,0 +1,99 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.design.R;
+import android.support.v4.widget.TextViewCompat;
+import android.support.v7.internal.view.menu.MenuItemImpl;
+import android.support.v7.internal.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class NavigationMenuItemView extends TextView implements MenuView.ItemView {
+
+    private MenuItemImpl mItemData;
+
+    private int mIconSize;
+
+    public NavigationMenuItemView(Context context) {
+        this(context, null);
+    }
+
+    public NavigationMenuItemView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mIconSize = context.getResources().getDimensionPixelSize(R.dimen.drawer_icon_size);
+    }
+
+    @Override
+    public void initialize(MenuItemImpl itemData, int menuType) {
+        mItemData = itemData;
+
+        setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
+
+        setTitle(itemData.getTitle());
+        setCheckable(itemData.isCheckable());
+        setIcon(itemData.getIcon());
+        setEnabled(itemData.isEnabled());
+    }
+
+    @Override
+    public MenuItemImpl getItemData() {
+        return mItemData;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        setText(title);
+    }
+
+    @Override
+    public void setCheckable(boolean checkable) {
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+    }
+
+    @Override
+    public void setShortcut(boolean showShortcut, char shortcutKey) {
+    }
+
+    @Override
+    public void setIcon(Drawable icon) {
+        if (icon != null) {
+            icon.setBounds(0, 0, mIconSize, mIconSize);
+        }
+        TextViewCompat.setCompoundDrawablesRelative(this, icon, null, null, null);
+    }
+
+    @Override
+    public boolean prefersCondensedTitle() {
+        return false;
+    }
+
+    @Override
+    public boolean showsIcon() {
+        return true;
+    }
+
+}
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
new file mode 100644
index 0000000..6e8e719
--- /dev/null
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -0,0 +1,349 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.design.R;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuItemImpl;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
+
+    private static final String STATE_HIERARCHY = "android:menu:list";
+
+    private NavigationMenuView mMenuView;
+
+    private Callback mCallback;
+
+    private MenuBuilder mMenu;
+
+    private ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
+
+    private int mId;
+
+    private NavigationMenuAdapter mAdapter;
+
+    private LayoutInflater mLayoutInflater;
+
+    private View mSpace;
+
+    /**
+     * Padding to be inserted at the top of the list to avoid the first menu item
+     * from being placed underneath the status bar.
+     */
+    private int mPaddingTopDefault;
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        mLayoutInflater = LayoutInflater.from(context);
+        mMenu = menu;
+        mPaddingTopDefault = context.getResources().getDimensionPixelOffset(
+                R.dimen.drawer_padding_top_default);
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        if (mMenuView == null) {
+            mMenuView = (NavigationMenuView) mLayoutInflater.inflate(
+                    R.layout.design_drawer_menu, root, false);
+            if (mAdapter == null) {
+                mAdapter = new NavigationMenuAdapter();
+            }
+            mMenuView.setAdapter(mAdapter);
+            mMenuView.setOnItemClickListener(this);
+        }
+        return mMenuView;
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        if (mAdapter != null) {
+            prepareMenuItems();
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Flattens the visible menu items of {@link #mMenu} into {@link #mItems},
+     * while inserting separators between items when necessary.
+     */
+    private void prepareMenuItems() {
+        mItems.clear();
+        int currentGroupId = 0;
+        for (MenuItemImpl item : mMenu.getVisibleItems()) {
+            if (item.hasSubMenu()) {
+                SubMenu subMenu = item.getSubMenu();
+                if (subMenu.hasVisibleItems()) {
+                    mItems.add(NavigationMenuItem.SEPARATOR);
+                    mItems.add(NavigationMenuItem.of(item));
+                    for (int i = 0, size = subMenu.size(); i < size; i++) {
+                        MenuItem subMenuItem = subMenu.getItem(i);
+                        if (subMenuItem.isVisible()) {
+                            mItems.add(NavigationMenuItem.of((MenuItemImpl) subMenuItem));
+                        }
+                    }
+                }
+            } else {
+                int groupId = item.getGroupId();
+                if (groupId != currentGroupId) {
+                    mItems.add(NavigationMenuItem.SEPARATOR);
+                }
+                mItems.add(NavigationMenuItem.of(item));
+                currentGroupId = groupId;
+            }
+        }
+    }
+
+    @Override
+    public void setCallback(Callback cb) {
+        mCallback = cb;
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        return false;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        if (mCallback != null) {
+            mCallback.onCloseMenu(menu, allMenusAreClosing);
+        }
+    }
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    @Override
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public int getId() {
+        return mId;
+    }
+
+    public void setId(int id) {
+        mId = id;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Bundle state = new Bundle();
+        SparseArray<Parcelable> hierarchy = new SparseArray<>();
+        if (mMenuView != null) {
+            mMenuView.saveHierarchyState(hierarchy);
+        }
+        state.putSparseParcelableArray(STATE_HIERARCHY, hierarchy);
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable parcelable) {
+        Bundle state = (Bundle) parcelable;
+        SparseArray<Parcelable> hierarchy = state.getSparseParcelableArray(STATE_HIERARCHY);
+        if (hierarchy != null) {
+            mMenuView.restoreHierarchyState(hierarchy);
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        int positionInAdapter = position - mMenuView.getHeaderViewsCount();
+        if (positionInAdapter >= 0) {
+            mMenu.performItemAction(mAdapter.getItem(positionInAdapter).getMenuItem(), this, 0);
+        }
+    }
+
+    public View inflateHeaderView(@LayoutRes int res) {
+        View view = mLayoutInflater.inflate(res, mMenuView, false);
+        addHeaderView(view);
+        onHeaderAdded();
+        return view;
+    }
+
+    public void addHeaderView(@NonNull View view) {
+        mMenuView.addHeaderView(view);
+        onHeaderAdded();
+    }
+
+    private void onHeaderAdded() {
+        // If we have just added the first header, we also need to insert a space
+        // between the header and the menu items.
+        if (mMenuView.getHeaderViewsCount() == 1) {
+            mSpace = mLayoutInflater.inflate(R.layout.design_drawer_item_space, mMenuView, false);
+            mMenuView.addHeaderView(mSpace);
+        }
+        // The padding on top should be cleared.
+        mMenuView.setPadding(0, 0, 0, 0);
+    }
+
+    public void removeHeaderView(@NonNull View view) {
+        if (mMenuView.removeHeaderView(view)) {
+            // Remove the space if it is the only remained header
+            if (mMenuView.getHeaderViewsCount() == 1) {
+                mMenuView.removeHeaderView(mSpace);
+                mMenuView.setPadding(0, mPaddingTopDefault, 0, 0);
+            }
+        }
+    }
+
+    private class NavigationMenuAdapter extends BaseAdapter {
+
+        private static final int VIEW_TYPE_NORMAL = 0;
+
+        private static final int VIEW_TYPE_SUBHEADER = 1;
+
+        private static final int VIEW_TYPE_SEPARATOR = 2;
+
+        @Override
+        public int getCount() {
+            return mItems.size();
+        }
+
+        @Override
+        public NavigationMenuItem getItem(int position) {
+            return mItems.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 3;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            NavigationMenuItem item = getItem(position);
+            if (item.isSeparator()) {
+                return VIEW_TYPE_SEPARATOR;
+            } else if (item.getMenuItem().hasSubMenu()) {
+                return VIEW_TYPE_SUBHEADER;
+            } else {
+                return VIEW_TYPE_NORMAL;
+            }
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            NavigationMenuItem item = getItem(position);
+            int viewType = getItemViewType(position);
+            switch (viewType) {
+                case VIEW_TYPE_NORMAL:
+                    if (convertView == null) {
+                        convertView = mLayoutInflater.inflate(R.layout.design_drawer_item, parent,
+                                false);
+                    }
+                    MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+                    itemView.initialize(item.getMenuItem(), 0);
+                    break;
+                case VIEW_TYPE_SUBHEADER:
+                    if (convertView == null) {
+                        convertView = mLayoutInflater.inflate(R.layout.design_drawer_item_subheader,
+                                parent, false);
+                    }
+                    TextView subHeader = (TextView) convertView;
+                    subHeader.setText(item.getMenuItem().getTitle());
+                    break;
+                case VIEW_TYPE_SEPARATOR:
+                    if (convertView == null) {
+                        convertView = mLayoutInflater.inflate(R.layout.design_drawer_item_separator,
+                                parent, false);
+                    }
+                    break;
+            }
+            return convertView;
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItem(position).isEnabled();
+        }
+
+    }
+
+    /**
+     * Wraps {@link MenuItemImpl}. This allows separators to be counted as items in list.
+     */
+    private static class NavigationMenuItem {
+
+        private static final NavigationMenuItem SEPARATOR = new NavigationMenuItem(null);
+
+        private final MenuItemImpl mMenuItem;
+
+        private NavigationMenuItem(MenuItemImpl item) {
+            mMenuItem = item;
+        }
+
+        public static NavigationMenuItem of(MenuItemImpl item) {
+            return new NavigationMenuItem(item);
+        }
+
+        public boolean isSeparator() {
+            return this == SEPARATOR;
+        }
+
+        public MenuItemImpl getMenuItem() {
+            return mMenuItem;
+        }
+
+        public boolean isEnabled() {
+            // Separators and subheaders never respond to click
+            return mMenuItem != null && !mMenuItem.hasSubMenu() && mMenuItem.isEnabled();
+        }
+
+    }
+
+}
diff --git a/design/src/android/support/design/internal/NavigationMenuView.java b/design/src/android/support/design/internal/NavigationMenuView.java
new file mode 100644
index 0000000..054b800
--- /dev/null
+++ b/design/src/android/support/design/internal/NavigationMenuView.java
@@ -0,0 +1,52 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuView;
+import android.util.AttributeSet;
+import android.widget.ListView;
+
+/**
+ * @hide
+ */
+public class NavigationMenuView extends ListView implements MenuView {
+
+    public NavigationMenuView(Context context) {
+        this(context, null);
+    }
+
+    public NavigationMenuView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void initialize(MenuBuilder menu) {
+
+    }
+
+    @Override
+    public int getWindowAnimations() {
+        return 0;
+    }
+
+}
diff --git a/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java b/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java
new file mode 100644
index 0000000..246d5b3
--- /dev/null
+++ b/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.design.R;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.WindowInsetsCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public class ScrimInsetsFrameLayout extends FrameLayout {
+
+    private Drawable mInsetForeground;
+
+    private Rect mInsets;
+
+    private Rect mTempRect = new Rect();
+
+    public ScrimInsetsFrameLayout(Context context) {
+        this(context, null);
+    }
+
+    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.ScrimInsetsFrameLayout, defStyleAttr,
+                R.style.Widget_Design_ScrimInsetsFrameLayout);
+        mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground);
+        a.recycle();
+        setWillNotDraw(true); // No need to draw until the insets are adjusted
+
+        ViewCompat.setOnApplyWindowInsetsListener(this,
+                new android.support.v4.view.OnApplyWindowInsetsListener() {
+                    @Override
+                    public WindowInsetsCompat onApplyWindowInsets(View v,
+                            WindowInsetsCompat insets) {
+                        if (null == mInsets) {
+                            mInsets = new Rect();
+                        }
+                        mInsets.set(insets.getSystemWindowInsetLeft(),
+                                insets.getSystemWindowInsetTop(),
+                                insets.getSystemWindowInsetRight(),
+                                insets.getSystemWindowInsetBottom());
+                        setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null);
+                        ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);
+                        return insets.consumeSystemWindowInsets();
+                    }
+                });
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        super.draw(canvas);
+
+        int width = getWidth();
+        int height = getHeight();
+        if (mInsets != null && mInsetForeground != null) {
+            int sc = canvas.save();
+            canvas.translate(getScrollX(), getScrollY());
+
+            // Top
+            mTempRect.set(0, 0, width, mInsets.top);
+            mInsetForeground.setBounds(mTempRect);
+            mInsetForeground.draw(canvas);
+
+            // Bottom
+            mTempRect.set(0, height - mInsets.bottom, width, height);
+            mInsetForeground.setBounds(mTempRect);
+            mInsetForeground.draw(canvas);
+
+            // Left
+            mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
+            mInsetForeground.setBounds(mTempRect);
+            mInsetForeground.draw(canvas);
+
+            // Right
+            mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
+            mInsetForeground.setBounds(mTempRect);
+            mInsetForeground.draw(canvas);
+
+            canvas.restoreToCount(sc);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mInsetForeground != null) {
+            mInsetForeground.setCallback(this);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mInsetForeground != null) {
+            mInsetForeground.setCallback(null);
+        }
+    }
+
+}
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
new file mode 100644
index 0000000..99085bd
--- /dev/null
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -0,0 +1,270 @@
+/*
+ * 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.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.design.R;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.ImageView;
+
+/**
+ * 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,
+ * launching, and the transferring anchor point.
+ *
+ * Floating action buttons come in two sizes: the default, which should be used in most cases, and
+ * the mini, which should only be used to create visual continuity with other elements on the
+ * screen.
+ */
+public class FloatingActionButton extends ImageView {
+
+    // These values must match those in the attrs declaration
+    private static final int SIZE_MINI = 1;
+    private static final int SIZE_NORMAL = 0;
+
+    private ColorStateList mBackgroundTint;
+    private PorterDuff.Mode mBackgroundTintMode;
+
+    private int mRippleColor;
+    private int mSize;
+    private int mContentPadding;
+
+    private final Rect mShadowPadding;
+
+    private final FloatingActionButtonImpl mImpl;
+
+    public FloatingActionButton(Context context) {
+        this(context, null);
+    }
+
+    public FloatingActionButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        mShadowPadding = new Rect();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.FloatingActionButton, defStyleAttr,
+                R.style.Widget_Design_FloatingActionButton);
+        Drawable background = a.getDrawable(R.styleable.FloatingActionButton_android_background);
+        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
+        mBackgroundTintMode = parseTintMode(a.getInt(
+                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
+        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
+        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
+        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
+        final float pressedTranslationZ = a.getDimension(
+                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
+        a.recycle();
+
+        final ShadowViewDelegate delegate = new ShadowViewDelegate() {
+            @Override
+            public float getRadius() {
+                return getSizeDimension() / 2f;
+            }
+
+            @Override
+            public void setShadowPadding(int left, int top, int right, int bottom) {
+                mShadowPadding.set(left, top, right, bottom);
+
+                setPadding(left + mContentPadding, top + mContentPadding,
+                        right + mContentPadding, bottom + mContentPadding);
+            }
+
+            @Override
+            public void setBackgroundDrawable(Drawable background) {
+                FloatingActionButton.super.setBackgroundDrawable(background);
+            }
+        };
+
+        if (Build.VERSION.SDK_INT >= 21) {
+            mImpl = new FloatingActionButtonLollipop(this, delegate);
+        } else {
+            mImpl = new FloatingActionButtonEclairMr1(this, delegate);
+        }
+
+        final int maxContentSize = (int) getResources().getDimension(R.dimen.fab_content_size);
+        mContentPadding = (getSizeDimension() - maxContentSize) / 2;
+
+        mImpl.setBackgroundDrawable(background, mBackgroundTint,
+                mBackgroundTintMode, mRippleColor);
+        mImpl.setElevation(elevation);
+        mImpl.setPressedTranslationZ(pressedTranslationZ);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int preferredSize = getSizeDimension();
+
+        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
+        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
+
+        // As we want to stay circular, we set both dimensions to be the
+        // smallest resolved dimension
+        final int d = Math.min(w, h);
+
+        // We add the shadow's padding to the measured dimension
+        setMeasuredDimension(
+                d + mShadowPadding.left + mShadowPadding.right,
+                d + mShadowPadding.top + mShadowPadding.bottom);
+    }
+
+    /**
+     * Set the ripple color for this {@link FloatingActionButton}.
+     * <p>
+     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
+     *
+     * @param color ARGB color to use for the ripple.
+     */
+    public void setRippleColor(int color) {
+        if (mRippleColor != color) {
+            mRippleColor = color;
+            mImpl.setRippleColor(color);
+        }
+    }
+
+    /**
+     * Return the tint applied to the background drawable, if specified.
+     *
+     * @return the tint applied to the background drawable
+     * @see #setBackgroundTintList(ColorStateList)
+     */
+    @Nullable
+    @Override
+    public ColorStateList getBackgroundTintList() {
+        return mBackgroundTint;
+    }
+
+    /**
+     * Applies a tint to the background drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     */
+    public void setBackgroundTintList(@Nullable ColorStateList tint) {
+        mImpl.setBackgroundTintList(tint);
+    }
+
+
+    /**
+     * Return the blending mode used to apply the tint to the background
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the background
+     *         drawable
+     * @see #setBackgroundTintMode(PorterDuff.Mode)
+     */
+    @Nullable
+    @Override
+    public PorterDuff.Mode getBackgroundTintMode() {
+        return mBackgroundTintMode;
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setBackgroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     */
+    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        mImpl.setBackgroundTintMode(tintMode);
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable background) {
+        mImpl.setBackgroundDrawable(background, mBackgroundTint, mBackgroundTintMode, mRippleColor);
+    }
+
+    final int getSizeDimension() {
+        switch (mSize) {
+            case SIZE_MINI:
+                return getResources().getDimensionPixelSize(R.dimen.fab_size_mini);
+            case SIZE_NORMAL:
+            default:
+                return getResources().getDimensionPixelSize(R.dimen.fab_size_normal);
+        }
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        mImpl.onDrawableStateChanged(getDrawableState());
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        super.jumpDrawablesToCurrentState();
+        mImpl.jumpDrawableToCurrentState();
+    }
+
+    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
+        int result = desiredSize;
+        int specMode = MeasureSpec.getMode(measureSpec);
+        int specSize = MeasureSpec.getSize(measureSpec);
+        switch (specMode) {
+            case MeasureSpec.UNSPECIFIED:
+                // Parent says we can be as big as we want. Just don't be larger
+                // than max size imposed on ourselves.
+                result = desiredSize;
+                break;
+            case MeasureSpec.AT_MOST:
+                // Parent says we can be as big as we want, up to specSize.
+                // Don't be larger than specSize, and don't be larger than
+                // the max size imposed on ourselves.
+                result = Math.min(desiredSize, specSize);
+                break;
+            case MeasureSpec.EXACTLY:
+                // No choice. Do what we are told.
+                result = specSize;
+                break;
+        }
+        return result;
+    }
+
+    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
+        switch (value) {
+            case 3:
+                return PorterDuff.Mode.SRC_OVER;
+            case 5:
+                return PorterDuff.Mode.SRC_IN;
+            case 9:
+                return PorterDuff.Mode.SRC_ATOP;
+            case 14:
+                return PorterDuff.Mode.MULTIPLY;
+            case 15:
+                return PorterDuff.Mode.SCREEN;
+            default:
+                return defaultMode;
+        }
+    }
+}
diff --git a/design/src/android/support/design/widget/NavigationDrawerView.java b/design/src/android/support/design/widget/NavigationDrawerView.java
new file mode 100644
index 0000000..b649948
--- /dev/null
+++ b/design/src/android/support/design/widget/NavigationDrawerView.java
@@ -0,0 +1,248 @@
+/*
+ * 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.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.design.R;
+import android.support.design.internal.NavigationMenuPresenter;
+import android.support.design.internal.ScrimInsetsFrameLayout;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.util.AttributeSet;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * Represents a standard navigation drawer for application. The drawer contents can be populated by
+ * a menu resource
+ * file.
+ * <p>NavigationDrawer needs to be placed inside a {@link android.support.v4.widget.DrawerLayout}.
+ * </p>
+ * <pre>
+ * &lt;android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *     xmlns:app="http://schemas.android.com/apk/res-auto"
+ *     android:id="@+id/drawer_layout"
+ *     android:layout_width="match_parent"
+ *     android:layout_height="match_parent"
+ *     android:fitsSystemWindows="true"&gt;
+ *
+ *     &lt;!-- Your contents --&gt;
+ *
+ *     &lt;android.support.design.widget.NavigationDrawerView
+ *         android:id="@+id/navigation_drawer"
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="match_parent"
+ *         android:layout_gravity="start" /&gt;
+ * &lt;/android.support.v4.widget.DrawerLayout&gt;
+ * </pre>
+ */
+public class NavigationDrawerView extends ScrimInsetsFrameLayout {
+
+    private static final int PRESENTER_NAVIGATION_DRAWER_VIEW = 1;
+
+    private OnNavigationItemSelectedListener mListener;
+
+    private MenuBuilder mMenu;
+
+    private NavigationMenuPresenter mPresenter;
+
+    private int mMaxWidth;
+
+    public NavigationDrawerView(Context context) {
+        this(context, null);
+    }
+
+    public NavigationDrawerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NavigationDrawerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        // Custom attributes
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.NavigationDrawerView, defStyleAttr,
+                R.style.Widget_Design_NavigationDrawerView);
+
+        //noinspection deprecation
+        setBackgroundDrawable(a.getDrawable(R.styleable.NavigationDrawerView_android_background));
+        ViewCompat.setElevation(this,
+                a.getDimensionPixelSize(R.styleable.NavigationDrawerView_android_elevation, 0));
+        ViewCompat.setFitsSystemWindows(this,
+                a.getBoolean(R.styleable.NavigationDrawerView_android_fitsSystemWindows, false));
+        mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationDrawerView_android_maxWidth, 0);
+        a.recycle();
+
+        // Set up the menu
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                return mListener != null && mListener.onNavigationItemSelected(item);
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {
+
+            }
+        });
+        mPresenter = new NavigationMenuPresenter();
+        mPresenter.setId(PRESENTER_NAVIGATION_DRAWER_VIEW);
+        mPresenter.initForMenu(context, mMenu);
+        mMenu.addMenuPresenter(mPresenter);
+        addView((View) mPresenter.getMenuView(this));
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState state = new SavedState(superState);
+        state.menuState = new Bundle();
+        mMenu.savePresenterStates(state.menuState);
+        return state;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable savedState) {
+        SavedState state = (SavedState) savedState;
+        super.onRestoreInstanceState(state.getSuperState());
+        mMenu.restorePresenterStates(state.menuState);
+    }
+
+    /**
+     * Set a listener that will be notified when a menu item is clicked.
+     *
+     * @param listener The listener to notify
+     */
+    public void setNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        switch (MeasureSpec.getMode(widthSpec)) {
+            case MeasureSpec.EXACTLY:
+                // Nothing to do
+                break;
+            case MeasureSpec.AT_MOST:
+                widthSpec = MeasureSpec.makeMeasureSpec(
+                        Math.min(MeasureSpec.getSize(widthSpec), mMaxWidth), MeasureSpec.EXACTLY);
+                break;
+            case MeasureSpec.UNSPECIFIED:
+                widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
+                break;
+        }
+        // Let super sort out the height
+        super.onMeasure(widthSpec, heightSpec);
+    }
+
+    /**
+     * @return The {@link Menu} associated with this NavigationDrawerView.
+     */
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    /**
+     * Inflates a View and add it as a header of the navigation menu.
+     *
+     * @param res The layout resource ID.
+     * @return a newly inflated View.
+     */
+    public View inflateHeaderView(@LayoutRes int res) {
+        return mPresenter.inflateHeaderView(res);
+    }
+
+    /**
+     * Adds a View as a header of the navigation menu.
+     *
+     * @param view The view to be added as a header of the navigation menu.
+     */
+    public void addHeaderView(@NonNull View view) {
+        mPresenter.addHeaderView(view);
+    }
+
+    /**
+     * Removes a previously-added header view.
+     *
+     * @param view The view to remove
+     */
+    public void removeHeaderView(@NonNull View view) {
+        mPresenter.removeHeaderView(view);
+    }
+
+    /**
+     * Listener for handling events on navigation drawer items.
+     */
+    public interface OnNavigationItemSelectedListener {
+
+        /**
+         * Called when an item in the navigation drawer is selected.
+         *
+         * @param item The selected item
+         */
+        public boolean onNavigationItemSelected(MenuItem item);
+    }
+
+    /**
+     * User interface state that is stored by NavigationDrawerView for implementing
+     * onSaveInstanceState().
+     */
+    public static class SavedState extends BaseSavedState {
+
+        public Bundle menuState;
+
+        public SavedState(Parcel in) {
+            super(in);
+            menuState = in.readBundle();
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeBundle(menuState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+
+            @Override
+            public SavedState createFromParcel(Parcel parcel) {
+                return new SavedState(parcel);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+
+        };
+
+    }
+
+}
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
new file mode 100755
index 0000000..dade816
--- /dev/null
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -0,0 +1,1396 @@
+/*
+ * 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.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.support.design.R;
+import android.support.v4.view.GravityCompat;
+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.CompatTextView;
+import android.support.v7.internal.widget.TintManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+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;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * 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
+ * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)}
+ * respectively. To display the tab, you need to add it to the layout via one of the {@link
+ * #addTab(Tab)} methods. For example:
+ * <pre>
+ * TabLayout tabLayout = ...;
+ * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
+ * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
+ * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
+ * </pre>
+ * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
+ * notified when any tab's selection state has been changed.
+ * <p>
+ * If you're using a {@link android.support.v4.view.ViewPager} together
+ * with this layout, you can use {@link #addTabsFromPagerAdapter(PagerAdapter)} which will populate
+ * the tabs using the {@link PagerAdapter}'s page titles. You should also use a {@link
+ * ViewPager.OnPageChangeListener} to forward the scroll and selection changes to this layout.
+ * You can use the one returned {@link #createOnPageChangeListener()} for easy implementation:
+ * <pre>
+ * ViewPager viewPager = ...;
+ * TabLayout tabLayout = ...;
+ * viewPager.setOnPageChangeListener(tabLayout.createOnPageChangeListener());
+ * </pre>
+ *
+ * @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
+ */
+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
+    private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps
+    private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps
+    private static final int MOTION_NON_ADJACENT_OFFSET = 24;
+
+    private static final int ANIMATION_DURATION = 300;
+
+    /**
+     * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab
+     * labels and a larger number of tabs. They are best used for browsing contexts in touch
+     * interfaces when users don’t need to directly compare the tab labels.
+     *
+     * @attr android.support.design.R.attr.tabMode
+     * @see #setTabMode(int)
+     * @see #getTabMode()
+     */
+    public static final int MODE_SCROLLABLE = 0;
+
+    /**
+     * Fixed tabs display all tabs concurrently and are best used with content that benefits from
+     * quick pivots between tabs. The maximum number of tabs is limited by the view’s width.
+     * Fixed tabs have equal width, based on the widest tab label.
+     *
+     * @attr android.support.design.R.attr.tabMode
+     * @see #setTabMode(int)
+     * @see #getTabMode()
+     */
+    public static final int MODE_FIXED = 1;
+
+    /**
+     * @hide
+     */
+    @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {}
+
+    /**
+     * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect
+     * when used with {@link #MODE_FIXED}.
+     *
+     * @attr android.support.design.R.attr.tabGravity
+     * @see #setTabGravity(int)
+     * @see #getTabGravity()
+     */
+    public static final int GRAVITY_FILL = 0;
+
+    /**
+     * Gravity used to lay out the tabs in the center of the {@link TabLayout}.
+     *
+     * @attr android.support.design.R.attr.tabGravity
+     * @see #setTabGravity(int)
+     * @see #getTabGravity()
+     */
+    public static final int GRAVITY_CENTER = 1;
+
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TabGravity {}
+
+    /**
+     * Callback interface invoked when a tab's selection state changes.
+     */
+    public interface OnTabSelectedListener {
+
+        /**
+         * Called when a tab enters the selected state.
+         *
+         * @param tab The tab that was selected
+         */
+        public void onTabSelected(Tab tab);
+
+        /**
+         * Called when a tab exits the selected state.
+         *
+         * @param tab The tab that was unselected
+         */
+        public void onTabUnselected(Tab tab);
+
+        /**
+         * Called when a tab that is already selected is chosen again by the user. Some applications
+         * may use this action to return to the top level of a category.
+         *
+         * @param tab The tab that was reselected.
+         */
+        public void onTabReselected(Tab tab);
+    }
+
+    private final ArrayList<Tab> mTabs = new ArrayList<>();
+    private Tab mSelectedTab;
+
+    private final SlidingTabStrip mTabStrip;
+
+    private int mTabPaddingStart;
+    private int mTabPaddingTop;
+    private int mTabPaddingEnd;
+    private int mTabPaddingBottom;
+
+    private final int mTabTextAppearance;
+    private int mTabSelectedTextColor;
+    private boolean mTabSelectedTextColorSet;
+    private final int mTabBackgroundResId;
+
+    private final int mTabMinWidth;
+    private int mTabMaxWidth;
+    private final int mRequestedTabMaxWidth;
+
+    private int mContentInsetStart;
+
+    private int mTabGravity;
+    private int mMode;
+
+    private OnTabSelectedListener mOnTabSelectedListener;
+    private View.OnClickListener mTabClickListener;
+
+    public TabLayout(Context context) {
+        this(context, null);
+    }
+
+    public TabLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        // Disable the Scroll Bar
+        setHorizontalScrollBarEnabled(false);
+        // Set us to fill the View port
+        setFillViewport(true);
+
+        // Add the TabStrip
+        mTabStrip = new SlidingTabStrip(context);
+        addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
+                defStyleAttr, R.style.Widget_Design_TabLayout);
+
+        mTabStrip.setSelectedIndicatorHeight(
+                a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0));
+        mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0));
+
+        mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance,
+                R.style.TextAppearance_Design_Tab);
+
+        mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a
+                .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0);
+        mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart,
+                mTabPaddingStart);
+        mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop,
+                mTabPaddingTop);
+        mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd,
+                mTabPaddingEnd);
+        mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom,
+                mTabPaddingBottom);
+
+        if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) {
+            mTabSelectedTextColor = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0);
+            mTabSelectedTextColorSet = true;
+        }
+
+        mTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, 0);
+        mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, 0);
+        mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0);
+        mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0);
+        mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED);
+        mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL);
+        a.recycle();
+
+        // Now apply the tab mode and gravity
+        applyModeAndGravity();
+    }
+
+    /**
+     * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
+     * 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.
+     */
+    public void setScrollPosition(int position, float positionOffset) {
+        if (isAnimationRunning(getAnimation())) {
+            return;
+        }
+        if (position < 0 || position >= mTabStrip.getChildCount()) {
+            return;
+        }
+
+        // Set the indicator position and update the scroll to match
+        mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
+        scrollTo(calculateScrollXForTab(position, positionOffset), 0);
+
+        // Update the 'selected state' view as we scroll
+        setSelectedTabView(Math.round(position + positionOffset));
+    }
+
+    /**
+     * Add new {@link Tab}s populated from a {@link PagerAdapter}. Each tab will have it's text set
+     * to the value returned from {@link PagerAdapter#getPageTitle(int)}.
+     *
+     * @param adapter the adapter to populate from
+     */
+    public void addTabsFromPagerAdapter(PagerAdapter adapter) {
+        for (int i = 0, count = adapter.getCount(); i < count; i++) {
+            addTab(newTab().setText(adapter.getPageTitle(i)));
+        }
+    }
+
+    /**
+     * Create a {@link ViewPager.OnPageChangeListener} which implements the
+     * necessary calls back to this layout so that the tabs position is kept in sync.
+     * <p>
+     * If you need to have a custom {@link ViewPager.OnPageChangeListener} for your own
+     * purposes, you can still use the instance returned from this method, but making sure to call
+     * through to all of the methods.
+     */
+    public ViewPager.OnPageChangeListener createOnPageChangeListener() {
+        return new ViewPager.SimpleOnPageChangeListener() {
+            @Override
+            public void onPageScrolled(int position, float positionOffset,
+                    int positionOffsetPixels) {
+                setScrollPosition(position, positionOffset);
+            }
+
+            @Override
+            public void onPageSelected(int position) {
+                getTabAt(position).select();
+            }
+        };
+    }
+
+    /**
+     * Add a tab to this layout. The tab will be added at the end of the list.
+     * If this is the first tab to be added it will become the selected tab.
+     *
+     * @param tab Tab to add
+     */
+    public void addTab(Tab tab) {
+        addTab(tab, mTabs.isEmpty());
+    }
+
+    /**
+     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
+     * If this is the first tab to be added it will become the selected tab.
+     *
+     * @param tab The tab to add
+     * @param position The new position of the tab
+     */
+    public void addTab(Tab tab, int position) {
+        addTab(tab, position, mTabs.isEmpty());
+    }
+
+    /**
+     * Add a tab to this layout. The tab will be added at the end of the list.
+     *
+     * @param tab Tab to add
+     * @param setSelected True if the added tab should become the selected tab.
+     */
+    public void addTab(Tab tab, boolean setSelected) {
+        if (tab.mParent != this) {
+            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
+        }
+
+        addTabView(tab, setSelected);
+        configureTab(tab, mTabs.size());
+        if (setSelected) {
+            tab.select();
+        }
+    }
+
+    /**
+     * Add a tab to this layout. The tab will be inserted at <code>position</code>.
+     *
+     * @param tab The tab to add
+     * @param position The new position of the tab
+     * @param setSelected True if the added tab should become the selected tab.
+     */
+    public void addTab(Tab tab, int position, boolean setSelected) {
+        if (tab.mParent != this) {
+            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
+        }
+
+        addTabView(tab, position, setSelected);
+        configureTab(tab, position);
+        if (setSelected) {
+            tab.select();
+        }
+    }
+
+    /**
+     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will handle switching to and from tabs.
+     *
+     * @param onTabSelectedListener Listener to handle tab selection events
+     */
+    public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
+        mOnTabSelectedListener = onTabSelectedListener;
+    }
+
+    /**
+     * Create and return a new {@link Tab}. You need to manually add this using
+     * {@link #addTab(Tab)} or a related method.
+     *
+     * @return A new Tab
+     * @see #addTab(Tab)
+     */
+    public Tab newTab() {
+        return new Tab(this);
+    }
+
+    /**
+     * Returns the number of tabs currently registered with the action bar.
+     *
+     * @return Tab count
+     */
+    public int getTabCount() {
+        return mTabs.size();
+    }
+
+    /**
+     * Returns the tab at the specified index.
+     */
+    public Tab getTabAt(int index) {
+        return mTabs.get(index);
+    }
+
+    /**
+     * Remove a tab from the layout. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
+     *
+     * @param tab The tab to remove
+     */
+    public void removeTab(Tab tab) {
+        if (tab.mParent != this) {
+            throw new IllegalArgumentException("Tab does not belong to this TabLayout.");
+        }
+
+        removeTabAt(tab.getPosition());
+    }
+
+    /**
+     * Remove a tab from the layout. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
+     *
+     * @param position Position of the tab to remove
+     */
+    public void removeTabAt(int position) {
+        final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0;
+        removeTabViewAt(position);
+
+        Tab removedTab = mTabs.remove(position);
+        if (removedTab != null) {
+            removedTab.setPosition(Tab.INVALID_POSITION);
+        }
+
+        final int newTabCount = mTabs.size();
+        for (int i = position; i < newTabCount; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+
+        if (selectedTabPosition == position) {
+            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+        }
+    }
+
+    /**
+     * Remove all tabs from the action bar and deselect the current tab.
+     */
+    public void removeAllTabs() {
+        // Remove all the views
+        mTabStrip.removeAllViews();
+
+        for (Iterator<Tab> i = mTabs.iterator(); i.hasNext(); ) {
+            Tab tab = i.next();
+            tab.setPosition(Tab.INVALID_POSITION);
+            i.remove();
+        }
+    }
+
+    /**
+     * Set the behavior mode for the Tabs in this layout. The valid input options are:
+     * <ul>
+     * <li>{@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used
+     * with content that benefits from quick pivots between tabs.</li>
+     * <li>{@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment,
+     * and can contain longer tab labels and a larger number of tabs. They are best used for
+     * browsing contexts in touch interfaces when users don’t need to directly compare the tab
+     * labels. This mode is commonly used with a {@link ViewPager}.</li>
+     * </ul>
+     *
+     * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
+     */
+    public void setTabMode(@Mode int mode) {
+        if (mode != mMode) {
+            mMode = mode;
+            applyModeAndGravity();
+        }
+    }
+
+    /**
+     * Returns the current mode used by this {@link TabLayout}.
+     *
+     * @see #setTabMode(int)
+     */
+    @Mode
+    public int getTabMode() {
+        return mMode;
+    }
+
+    /**
+     * Set the gravity to use when laying out the tabs.
+     *
+     * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
+     */
+    public void setTabGravity(@TabGravity int gravity) {
+        if (mTabGravity != gravity) {
+            mTabGravity = gravity;
+            applyModeAndGravity();
+        }
+    }
+
+    /**
+     * The current gravity used for laying out tabs.
+     *
+     * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
+     */
+    @TabGravity
+    public int getTabGravity() {
+        return mTabGravity;
+    }
+
+    /**
+     * Set the text color to use when a tab is selected.
+     *
+     * @param textColor
+     */
+    public void setTabSelectedTextColor(int textColor) {
+        if (!mTabSelectedTextColorSet || mTabSelectedTextColor != textColor) {
+            mTabSelectedTextColor = textColor;
+            mTabSelectedTextColorSet = true;
+
+            for (int i = 0, z = mTabStrip.getChildCount(); i < z; i++) {
+                updateTab(i);
+            }
+        }
+    }
+
+    /**
+     * Returns the text color currently used when a tab is selected.
+     */
+    public int getTabSelectedTextColor() {
+        return mTabSelectedTextColor;
+    }
+
+    private TabView createTabView(Tab tab) {
+        final TabView tabView = new TabView(getContext(), tab);
+        tabView.setFocusable(true);
+
+        if (mTabClickListener == null) {
+            mTabClickListener = new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    TabView tabView = (TabView) view;
+                    tabView.getTab().select();
+                }
+            };
+        }
+        tabView.setOnClickListener(mTabClickListener);
+        return tabView;
+    }
+
+    private void configureTab(Tab tab, int position) {
+        tab.setPosition(position);
+        mTabs.add(position, tab);
+
+        final int count = mTabs.size();
+        for (int i = position + 1; i < count; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+    }
+
+    private void updateTab(int position) {
+        final TabView view = (TabView) mTabStrip.getChildAt(position);
+        if (view != null) {
+            view.update();
+        }
+    }
+
+    private void addTabView(Tab tab, boolean setSelected) {
+        final TabView tabView = createTabView(tab);
+        mTabStrip.addView(tabView, createLayoutParamsForTabs());
+        if (setSelected) {
+            tabView.setSelected(true);
+        }
+    }
+
+    private void addTabView(Tab tab, int position, boolean setSelected) {
+        final TabView tabView = createTabView(tab);
+        mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
+        if (setSelected) {
+            tabView.setSelected(true);
+        }
+    }
+
+    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
+        final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+        updateTabViewLayoutParams(lp);
+        return lp;
+    }
+
+    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
+        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
+            lp.width = 0;
+            lp.weight = 1;
+        } else {
+            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
+            lp.weight = 0;
+        }
+    }
+
+    private int dpToPx(int dps) {
+        return Math.round(getResources().getDisplayMetrics().density * dps);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // If we have a MeasureSpec which allows us to decide our height, try and use the default
+        // height
+        switch (MeasureSpec.getMode(heightMeasureSpec)) {
+            case MeasureSpec.AT_MOST:
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        Math.min(dpToPx(DEFAULT_HEIGHT), MeasureSpec.getSize(heightMeasureSpec)),
+                        MeasureSpec.EXACTLY);
+                break;
+            case MeasureSpec.UNSPECIFIED:
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(DEFAULT_HEIGHT),
+                        MeasureSpec.EXACTLY);
+                break;
+        }
+
+        // Now super measure itself using the (possibly) modified height spec
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (mMode == MODE_FIXED && getChildCount() == 1) {
+            // If we're in fixed mode then we need to make the tab strip is the same width as us
+            // so we don't scroll
+            final View child = getChildAt(0);
+            final int width = getMeasuredWidth();
+
+            if (child.getMeasuredWidth() > width) {
+                // If the child is wider than us, re-measure it with a widthSpec set to exact our
+                // measure width
+                int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
+                        + getPaddingBottom(), child.getLayoutParams().height);
+                int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+            }
+        }
+
+        // Now update the tab max width. We do it here as the default tab min width is
+        // layout width - 56dp
+        int maxTabWidth = mRequestedTabMaxWidth;
+        final int defaultTabMaxWidth = getMeasuredWidth() - dpToPx(TAB_MIN_WIDTH_MARGIN);
+        if (maxTabWidth == 0 || maxTabWidth > defaultTabMaxWidth) {
+            // If the request tab max width is 0, or larger than our default, use the default
+            maxTabWidth = defaultTabMaxWidth;
+        }
+        mTabMaxWidth = maxTabWidth;
+    }
+
+    private void removeTabViewAt(int position) {
+        mTabStrip.removeViewAt(position);
+        requestLayout();
+    }
+
+    private void animateToTab(int newPosition) {
+        clearAnimation();
+
+        if (newPosition == Tab.INVALID_POSITION) {
+            return;
+        }
+        
+        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);
+            return;
+        }
+
+        final int startScrollX = getScrollX();
+        final int targetScrollX = calculateScrollXForTab(newPosition, 0);
+        final int duration = ANIMATION_DURATION;
+
+        if (startScrollX != targetScrollX) {
+            final Animation animation = new Animation() {
+                @Override
+                protected void applyTransformation(float interpolatedTime, Transformation t) {
+                    final float value = lerp(startScrollX, targetScrollX, interpolatedTime);
+                    scrollTo((int) value, 0);
+                }
+            };
+            animation.setInterpolator(INTERPOLATOR);
+            animation.setDuration(duration);
+            startAnimation(animation);
+        }
+
+        // Now animate the indicator
+        mTabStrip.animateIndicatorToPosition(newPosition, duration);
+    }
+
+    private void setSelectedTabView(int position) {
+        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);
+        }
+    }
+
+    private static boolean isAnimationRunning(Animation animation) {
+        return animation != null && animation.hasStarted() && !animation.hasEnded();
+    }
+
+    void selectTab(Tab tab) {
+        if (mSelectedTab == tab) {
+            if (mSelectedTab != null) {
+                if (mOnTabSelectedListener != null) {
+                    mOnTabSelectedListener.onTabReselected(mSelectedTab);
+                }
+                animateToTab(tab.getPosition());
+            }
+        } else {
+            final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
+            setSelectedTabView(newPosition);
+
+            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);
+            } else {
+                animateToTab(newPosition);
+            }
+
+            if (mSelectedTab != null && mOnTabSelectedListener != null) {
+                mOnTabSelectedListener.onTabUnselected(mSelectedTab);
+            }
+            mSelectedTab = tab;
+            if (mSelectedTab != null && mOnTabSelectedListener != null) {
+                mOnTabSelectedListener.onTabSelected(mSelectedTab);
+            }
+        }
+    }
+
+    private int calculateScrollXForTab(int position, float positionOffset) {
+        if (mMode == MODE_SCROLLABLE) {
+            final View selectedChild = mTabStrip.getChildAt(position);
+            final View nextChild = position + 1 < mTabStrip.getChildCount()
+                    ? mTabStrip.getChildAt(position + 1)
+                    : null;
+            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
+            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
+
+            return (int) (selectedChild.getLeft()
+                    + ((selectedWidth + nextWidth) * positionOffset * 0.5f)
+                    + selectedChild.getWidth() * 0.5f
+                    - getWidth() * 0.5f);
+        }
+        return 0;
+    }
+
+    private void applyModeAndGravity() {
+        int paddingStart = 0;
+        if (mMode == MODE_SCROLLABLE) {
+            // If we're scrollable, or fixed at start, inset using padding
+            paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
+        }
+        ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
+
+        switch (mMode) {
+            case MODE_FIXED:
+                mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
+                break;
+            case MODE_SCROLLABLE:
+                mTabStrip.setGravity(GravityCompat.START);
+                break;
+        }
+
+        updateTabViewsLayoutParams();
+    }
+
+    private void updateTabViewsLayoutParams() {
+        for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+            View child = mTabStrip.getChildAt(i);
+            updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
+            child.requestLayout();
+        }
+    }
+
+    /**
+     * A tab in this layout. Instances can be created via {@link #newTab()}.
+     */
+    public static final class Tab {
+
+        /**
+         * An invalid position for a tab.
+         *
+         * @see #getPosition()
+         */
+        public static final int INVALID_POSITION = -1;
+
+        private Object mTag;
+        private Drawable mIcon;
+        private CharSequence mText;
+        private CharSequence mContentDesc;
+        private int mPosition = INVALID_POSITION;
+        private View mCustomView;
+
+        private final TabLayout mParent;
+
+        Tab(TabLayout parent) {
+            mParent = parent;
+        }
+
+        /**
+         * @return This Tab's tag object.
+         */
+        public Object getTag() {
+            return mTag;
+        }
+
+        /**
+         * Give this Tab an arbitrary object to hold for later use.
+         *
+         * @param tag Object to store
+         * @return The current instance for call chaining
+         */
+        public Tab setTag(Object tag) {
+            mTag = tag;
+            return this;
+        }
+
+        View getCustomView() {
+            return mCustomView;
+        }
+
+        /**
+         * Set a custom view to be used for this tab. This overrides values set by {@link
+         * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         *
+         * @param view Custom view to be used as a tab.
+         * @return The current instance for call chaining
+         */
+        public Tab setCustomView(View view) {
+            mCustomView = view;
+            if (mPosition >= 0) {
+                mParent.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Set a custom view to be used for this tab. This overrides values set by {@link
+         * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         *
+         * @param layoutResId A layout resource to inflate and use as a custom tab view
+         * @return The current instance for call chaining
+         */
+        public Tab setCustomView(int layoutResId) {
+            return setCustomView(
+                    LayoutInflater.from(mParent.getContext()).inflate(layoutResId, null));
+        }
+
+        /**
+         * Return the icon associated with this tab.
+         *
+         * @return The tab's icon
+         */
+        public Drawable getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * Return the current position of this tab in the action bar.
+         *
+         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
+         * the action bar.
+         */
+        public int getPosition() {
+            return mPosition;
+        }
+
+        void setPosition(int position) {
+            mPosition = position;
+        }
+
+        /**
+         * Return the text of this tab.
+         *
+         * @return The tab's text
+         */
+        public CharSequence getText() {
+            return mText;
+        }
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param icon The drawable to use as an icon
+         * @return The current instance for call chaining
+         */
+        public Tab setIcon(Drawable icon) {
+            mIcon = icon;
+            if (mPosition >= 0) {
+                mParent.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param resId A resource ID referring to the icon that should be displayed
+         * @return The current instance for call chaining
+         */
+        public Tab setIcon(int resId) {
+            return setIcon(TintManager.getDrawable(mParent.getContext(), resId));
+        }
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not room to display
+         * the entire string.
+         *
+         * @param text The text to display
+         * @return The current instance for call chaining
+         */
+        public Tab setText(CharSequence text) {
+            mText = text;
+            if (mPosition >= 0) {
+                mParent.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not room to display
+         * the entire string.
+         *
+         * @param resId A resource ID referring to the text that should be displayed
+         * @return The current instance for call chaining
+         */
+        public Tab setText(int resId) {
+            return setText(mParent.getResources().getText(resId));
+        }
+
+        /**
+         * Select this tab. Only valid if the tab has been added to the action bar.
+         */
+        public void select() {
+            mParent.selectTab(this);
+        }
+
+        /**
+         * Set a description of this tab's content for use in accessibility support. If no content
+         * description is provided the title will be used.
+         *
+         * @param resId A resource ID referring to the description text
+         * @return The current instance for call chaining
+         * @see #setContentDescription(CharSequence)
+         * @see #getContentDescription()
+         */
+        public Tab setContentDescription(int resId) {
+            return setContentDescription(mParent.getResources().getText(resId));
+        }
+
+        /**
+         * Set a description of this tab's content for use in accessibility support. If no content
+         * description is provided the title will be used.
+         *
+         * @param contentDesc Description of this tab's content
+         * @return The current instance for call chaining
+         * @see #setContentDescription(int)
+         * @see #getContentDescription()
+         */
+        public Tab setContentDescription(CharSequence contentDesc) {
+            mContentDesc = contentDesc;
+            if (mPosition >= 0) {
+                mParent.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Gets a brief description of this tab's content for use in accessibility support.
+         *
+         * @return Description of this tab's content
+         * @see #setContentDescription(CharSequence)
+         * @see #setContentDescription(int)
+         */
+        public CharSequence getContentDescription() {
+            return mContentDesc;
+        }
+    }
+
+    class TabView extends LinearLayout implements OnLongClickListener {
+        private final Tab mTab;
+        private TextView mTextView;
+        private ImageView mIconView;
+        private View mCustomView;
+
+        public TabView(Context context, Tab tab) {
+            super(context);
+            mTab = tab;
+            if (mTabBackgroundResId != 0) {
+                setBackgroundDrawable(TintManager.getDrawable(context, mTabBackgroundResId));
+            }
+            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
+                    mTabPaddingEnd, mTabPaddingBottom);
+            setGravity(Gravity.CENTER);
+            update();
+        }
+
+        @Override
+        public void setSelected(boolean selected) {
+            final boolean changed = (isSelected() != selected);
+            super.setSelected(selected);
+            if (changed && selected) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+
+                if (mTextView != null) {
+                    mTextView.setSelected(selected);
+                }
+                if (mIconView != null) {
+                    mIconView.setSelected(selected);
+                }
+            }
+        }
+
+        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+        @Override
+        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(event);
+            // This view masquerades as an action bar tab.
+            event.setClassName(ActionBar.Tab.class.getName());
+        }
+
+        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+        @Override
+        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(info);
+            // This view masquerades as an action bar tab.
+            info.setClassName(ActionBar.Tab.class.getName());
+        }
+
+        @Override
+        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            if (getMeasuredWidth() > mTabMaxWidth) {
+                // Re-measure if we went beyond our maximum size.
+                super.onMeasure(MeasureSpec.makeMeasureSpec(
+                        mTabMaxWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
+            } else if (mTabMinWidth > 0 && getMeasuredHeight() < mTabMinWidth) {
+                // Re-measure if we're below our minimum size.
+                super.onMeasure(MeasureSpec.makeMeasureSpec(
+                        mTabMinWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
+            }
+        }
+
+        final void update() {
+            final Tab tab = mTab;
+            final View custom = tab.getCustomView();
+            if (custom != null) {
+                final ViewParent customParent = custom.getParent();
+                if (customParent != this) {
+                    if (customParent != null) {
+                        ((ViewGroup) customParent).removeView(custom);
+                    }
+                    addView(custom);
+                }
+                mCustomView = custom;
+                if (mTextView != null) {
+                    mTextView.setVisibility(GONE);
+                }
+                if (mIconView != null) {
+                    mIconView.setVisibility(GONE);
+                    mIconView.setImageDrawable(null);
+                }
+            } else {
+                if (mCustomView != null) {
+                    removeView(mCustomView);
+                    mCustomView = null;
+                }
+
+                final Drawable icon = tab.getIcon();
+                final CharSequence text = tab.getText();
+
+                if (icon != null) {
+                    if (mIconView == null) {
+                        ImageView iconView = new ImageView(getContext());
+                        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                                LayoutParams.WRAP_CONTENT);
+                        lp.gravity = Gravity.CENTER_VERTICAL;
+                        iconView.setLayoutParams(lp);
+                        addView(iconView, 0);
+                        mIconView = iconView;
+                    }
+                    mIconView.setImageDrawable(icon);
+                    mIconView.setVisibility(VISIBLE);
+                } else if (mIconView != null) {
+                    mIconView.setVisibility(GONE);
+                    mIconView.setImageDrawable(null);
+                }
+
+                final boolean hasText = !TextUtils.isEmpty(text);
+                if (hasText) {
+                    if (mTextView == null) {
+                        CompatTextView textView = new CompatTextView(getContext());
+                        textView.setTextAppearance(getContext(), mTabTextAppearance);
+                        textView.setMaxLines(MAX_TAB_TEXT_LINES);
+                        textView.setEllipsize(TextUtils.TruncateAt.END);
+                        textView.setGravity(Gravity.CENTER);
+                        if (mTabSelectedTextColorSet) {
+                            textView.setTextColor(createColorStateList(
+                                    textView.getCurrentTextColor(), mTabSelectedTextColor));
+                        }
+
+                        addView(textView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+                        mTextView = textView;
+                    }
+                    mTextView.setText(text);
+                    mTextView.setVisibility(VISIBLE);
+                } else if (mTextView != null) {
+                    mTextView.setVisibility(GONE);
+                    mTextView.setText(null);
+                }
+
+                if (mIconView != null) {
+                    mIconView.setContentDescription(tab.getContentDescription());
+                }
+
+                if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
+                    setOnLongClickListener(this);
+                } else {
+                    setOnLongClickListener(null);
+                    setLongClickable(false);
+                }
+            }
+        }
+
+        @Override
+        public boolean onLongClick(View v) {
+            final int[] screenPos = new int[2];
+            getLocationOnScreen(screenPos);
+
+            final Context context = getContext();
+            final int width = getWidth();
+            final int height = getHeight();
+            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
+                    Toast.LENGTH_SHORT);
+            // Show under the tab
+            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+                    (screenPos[0] + width / 2) - screenWidth / 2, height);
+
+            cheatSheet.show();
+            return true;
+        }
+
+        private ColorStateList createColorStateList(int defaultColor, int selectedColor) {
+            final int[][] states = new int[2][];
+            final int[] colors = new int[2];
+            int i = 0;
+
+            states[i] = SELECTED_STATE_SET;
+            colors[i] = selectedColor;
+            i++;
+
+            // Default enabled state
+            states[i] = EMPTY_STATE_SET;
+            colors[i] = defaultColor;
+            i++;
+
+            return new ColorStateList(states, colors);
+        }
+
+        public Tab getTab() {
+            return mTab;
+        }
+    }
+
+    private class SlidingTabStrip extends LinearLayout {
+        private int mSelectedIndicatorHeight;
+        private final Paint mSelectedIndicatorPaint;
+
+        private int mSelectedPosition = -1;
+        private float mSelectionOffset;
+
+        private int mIndicatorLeft = -1;
+        private int mIndicatorRight = -1;
+
+        SlidingTabStrip(Context context) {
+            super(context);
+            setWillNotDraw(false);
+            mSelectedIndicatorPaint = new Paint();
+        }
+
+        void setSelectedIndicatorColor(int color) {
+            mSelectedIndicatorPaint.setColor(color);
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+
+        void setSelectedIndicatorHeight(int height) {
+            mSelectedIndicatorHeight = height;
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+
+        void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
+            if (isAnimationRunning(getAnimation())) {
+                return;
+            }
+            mSelectedPosition = position;
+            mSelectionOffset = positionOffset;
+            updateIndicatorPosition();
+        }
+
+        @Override
+        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+            if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
+                // HorizontalScrollView will first measure use with UNSPECIFIED, and then with
+                // EXACTLY. Ignore the first call since anything we do will be overwritten anyway
+                return;
+            }
+
+            if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
+                final int count = getChildCount();
+
+                final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+                // First we'll find the largest tab
+                int largestTabWidth = 0;
+                for (int i = 0, z = count; i < z; i++) {
+                    final View child = getChildAt(i);
+                    child.measure(unspecifiedSpec, heightMeasureSpec);
+                    largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
+                }
+
+                if (largestTabWidth <= 0) {
+                    // If we don't have a largest child yet, skip until the next measure pass
+                    return;
+                }
+
+                final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
+                if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
+                    // If the tabs fit within our width minus gutters, we will set all tabs to have
+                    // the same width
+                    for (int i = 0; i < count; i++) {
+                        final View child = getChildAt(i);
+                        final LinearLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                        lp.width = largestTabWidth;
+                        lp.weight = 0;
+                    }
+                } else {
+                    // If the tabs will wrap to be larger than the width minus gutters, we need
+                    // to switch to GRAVITY_FILL
+                    mTabGravity = GRAVITY_FILL;
+                    updateTabViewsLayoutParams();
+                }
+
+                // Now re-measure after our changes
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            super.onLayout(changed, l, t, r, b);
+
+            if (!isAnimationRunning(getAnimation())) {
+                // If we've been layed out, and we're not currently in an animation, update the
+                // indicator position
+                updateIndicatorPosition();
+            }
+        }
+
+        private void updateIndicatorPosition() {
+            final View selectedTitle = getChildAt(mSelectedPosition);
+            int left, right;
+
+            if (selectedTitle != null && selectedTitle.getWidth() > 0) {
+                left = selectedTitle.getLeft();
+                right = selectedTitle.getRight();
+
+                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
+                    // Draw the selection partway between the tabs
+                    View nextTitle = getChildAt(mSelectedPosition + 1);
+                    left = (int) (mSelectionOffset * nextTitle.getLeft() +
+                            (1.0f - mSelectionOffset) * left);
+                    right = (int) (mSelectionOffset * nextTitle.getRight() +
+                            (1.0f - mSelectionOffset) * right);
+                }
+            } else {
+                left = right = -1;
+            }
+
+            setIndicatorPosition(left, right);
+        }
+
+        private void setIndicatorPosition(int left, int right) {
+            if (left != mIndicatorLeft || right != mIndicatorRight) {
+                // If the indicator's left/right has changed, invalidate
+                mIndicatorLeft = left;
+                mIndicatorRight = right;
+                ViewCompat.postInvalidateOnAnimation(this);
+            }
+        }
+
+        void animateIndicatorToPosition(final int position, int duration) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(this)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+            final View targetView = getChildAt(position);
+            final int targetLeft = targetView.getLeft();
+            final int targetRight = targetView.getRight();
+            final int startLeft;
+            final int startRight;
+
+            if (Math.abs(position - mSelectedPosition) <= 1) {
+                // If the views are adjacent, we'll animate from edge-to-edge
+                startLeft = mIndicatorLeft;
+                startRight = mIndicatorRight;
+            } else {
+                // Else, we'll just grow from the nearest edge
+                final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);
+                if (position < mSelectedPosition) {
+                    // We're going end-to-start
+                    if (isRtl) {
+                        startLeft = startRight = targetLeft - offset;
+                    } else {
+                        startLeft = startRight = targetRight + offset;
+                    }
+                } else {
+                    // We're going start-to-end
+                    if (isRtl) {
+                        startLeft = startRight = targetRight + offset;
+                    } else {
+                        startLeft = startRight = targetLeft - offset;
+                    }
+                }
+            }
+
+            if (startLeft != targetLeft || startRight != targetRight) {
+                final Animation anim = new Animation() {
+                    @Override
+                    protected void applyTransformation(float interpolatedTime, Transformation t) {
+                        setIndicatorPosition(
+                                (int) lerp(startLeft, targetLeft, interpolatedTime),
+                                (int) lerp(startRight, targetRight, interpolatedTime));
+                    }
+                };
+                anim.setInterpolator(INTERPOLATOR);
+                anim.setDuration(duration);
+                anim.setAnimationListener(new Animation.AnimationListener() {
+                    @Override
+                    public void onAnimationStart(Animation animation) {
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animation animation) {
+                        mSelectedPosition = position;
+                        mSelectionOffset = 0f;
+                    }
+
+                    @Override
+                    public void onAnimationRepeat(Animation animation) {
+                    }
+                });
+
+                startAnimation(anim);
+            }
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            // Thick colored underline below the current selection
+            if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
+                canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
+                        mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+            }
+        }
+    }
+
+    /**
+     * 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/settings.gradle b/settings.gradle
index 856c8d9..7e907c2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -27,3 +27,6 @@
 
 include ':support-leanback-v17'
 project(':support-leanback-v17').projectDir = new File(rootDir, 'v17/leanback')
+
+include ':support-design'
+project(':support-design').projectDir = new File(rootDir, 'design')
diff --git a/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java b/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java
new file mode 100644
index 0000000..ea67375
--- /dev/null
+++ b/tests/java/android/support/v4/app/NotificationCompatActionWearableExtenderTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.tests.R;
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link android.support.v4.app.NotificationCompat.Action.WearableExtender}.
+ */
+public class NotificationCompatActionWearableExtenderTest extends AndroidTestCase {
+
+    private int mIcon;
+    private String mTitle = "Test Title";
+    private PendingIntent mPendingIntent;
+
+    private String mInProgress = "In Progress Label";
+    private String mConfirm = "Confirmation Label";
+    private String mCancel = "Cancelation Label";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mIcon = R.drawable.action_icon;
+        mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+    }
+
+    // Test that the default empty Extender is equal to the compat version.
+    public void testEmptyEquals() throws Exception {
+        assertExtendersEqual(new Notification.Action.WearableExtender(),
+                new NotificationCompat.Action.WearableExtender());
+    }
+
+    // Test that the fully populated Extender is equal to the compat version.
+    public void testFullEquals() throws Exception {
+        Notification.Action.WearableExtender baseExtender =
+            new Notification.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+        NotificationCompat.Action.WearableExtender compatExtender =
+            new NotificationCompat.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+        assertExtendersEqual(baseExtender, compatExtender);
+    }
+
+    // Test that the base WearableExtender from an empty Notification is equal to the compat.
+    public void testEmptyNotification() throws Exception {
+        Notification baseNotif = new Notification.Builder(getContext())
+                .build();
+        Notification compatNotif = new NotificationCompat.Builder(getContext())
+                .build();
+
+        assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+        assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+        assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+        assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+    }
+
+    public void testDefaultActionNotification() throws Exception {
+        Notification.Action.Builder baseAction =
+            new Notification.Action.Builder(mIcon, mTitle, mPendingIntent);
+        NotificationCompat.Action.Builder compatAction =
+            new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent);
+
+        Notification.WearableExtender baseNoteExtender =
+                new Notification.WearableExtender()
+                        .addAction(baseAction.build());
+        NotificationCompat.WearableExtender compatNoteExtender =
+                new NotificationCompat.WearableExtender()
+                        .addAction(compatAction.build());
+
+        Notification baseNotif = new Notification.Builder(getContext())
+                .extend(baseNoteExtender).build();
+        Notification compatNotif = new NotificationCompat.Builder(getContext())
+                .extend(compatNoteExtender).build();
+
+        assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+        assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+        assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+        assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+    }
+
+    public void testDefaultActionExtenderNotification() throws Exception {
+        Notification.Action.WearableExtender baseExtender =
+            new Notification.Action.WearableExtender();
+        NotificationCompat.Action.WearableExtender compatExtender =
+            new NotificationCompat.Action.WearableExtender();
+
+        Notification.Action.Builder baseAction =
+            new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(baseExtender);
+        NotificationCompat.Action.Builder compatAction =
+            new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(compatExtender);
+
+        Notification.WearableExtender baseNoteExtender =
+                new Notification.WearableExtender()
+                        .addAction(baseAction.build());
+        NotificationCompat.WearableExtender compatNoteExtender =
+                new NotificationCompat.WearableExtender()
+                        .addAction(compatAction.build());
+
+        Notification baseNotif = new Notification.Builder(getContext())
+                .extend(baseNoteExtender).build();
+        Notification compatNotif = new NotificationCompat.Builder(getContext())
+                .extend(compatNoteExtender).build();
+
+        assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+        assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+        assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+        assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+    }
+
+    public void testFullNotification() throws Exception {
+        Notification.Action.WearableExtender baseExtender =
+            new Notification.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+        NotificationCompat.Action.WearableExtender compatExtender =
+            new NotificationCompat.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+
+        Notification.Action.Builder baseAction =
+            new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(baseExtender);
+        NotificationCompat.Action.Builder compatAction =
+            new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(compatExtender);
+
+        Notification.WearableExtender baseNoteExtender =
+                new Notification.WearableExtender()
+                        .addAction(baseAction.build());
+        NotificationCompat.WearableExtender compatNoteExtender =
+                new NotificationCompat.WearableExtender()
+                        .addAction(compatAction.build());
+
+        Notification baseNotif = new Notification.Builder(getContext())
+                .extend(baseNoteExtender).build();
+        Notification compatNotif = new NotificationCompat.Builder(getContext())
+                .extend(compatNoteExtender).build();
+
+        assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+        assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+        assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+        assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+    }
+
+    public void testMultipleActionsInANotification() throws Exception {
+        Notification.Action.WearableExtender baseExtender1 =
+            new Notification.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+        NotificationCompat.Action.WearableExtender compatExtender1 =
+            new NotificationCompat.Action.WearableExtender()
+                .setAvailableOffline(true)
+                .setInProgressLabel(mInProgress)
+                .setConfirmLabel(mConfirm)
+                .setCancelLabel(mCancel);
+
+        Notification.Action.Builder baseAction1 =
+            new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(baseExtender1);
+        NotificationCompat.Action.Builder compatAction1 =
+            new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(compatExtender1);
+
+        Notification.Action.WearableExtender baseExtender2 =
+            new Notification.Action.WearableExtender()
+                .setAvailableOffline(false)
+                .setInProgressLabel("Alternate Label")
+                .setConfirmLabel("Duplicated Label")
+                .setCancelLabel("Duplicated Label");
+        NotificationCompat.Action.WearableExtender compatExtender2 =
+            new NotificationCompat.Action.WearableExtender()
+                .setAvailableOffline(false)
+                .setInProgressLabel("Alternate Label")
+                .setConfirmLabel("Duplicated Label")
+                .setCancelLabel("Duplicated Label");
+
+        Notification.Action.Builder baseAction2 =
+            new Notification.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(baseExtender2);
+        NotificationCompat.Action.Builder compatAction2 =
+            new NotificationCompat.Action.Builder(mIcon, mTitle, mPendingIntent)
+                .extend(compatExtender2);
+
+        Notification.WearableExtender baseNoteExtender =
+                new Notification.WearableExtender()
+                        .addAction(baseAction1.build())
+                        .addAction(new Notification.Action(R.drawable.action_icon2, "Action1",
+                                mPendingIntent))
+                        .addAction(baseAction2.build());
+        NotificationCompat.WearableExtender compatNoteExtender =
+                new NotificationCompat.WearableExtender()
+                        .addAction(compatAction1.build())
+                        .addAction(new NotificationCompat.Action(R.drawable.action_icon2,
+                                "Action1", mPendingIntent))
+                        .addAction(compatAction2.build());
+
+        Notification baseNotif = new Notification.Builder(getContext())
+                .extend(baseNoteExtender).build();
+        Notification compatNotif = new NotificationCompat.Builder(getContext())
+                .extend(compatNoteExtender).build();
+
+        assertExtendersFromNotificationEqual(baseNotif, baseNotif);
+        assertExtendersFromNotificationEqual(compatNotif, compatNotif);
+        assertExtendersFromNotificationEqual(baseNotif, compatNotif);
+        assertExtendersFromNotificationEqual(compatNotif, baseNotif);
+    }
+
+    private void assertExtendersEqual(Notification.Action.WearableExtender base,
+            NotificationCompat.Action.WearableExtender compat) {
+        assertEquals(base.isAvailableOffline(), compat.isAvailableOffline());
+        assertEquals(base.getInProgressLabel(), compat.getInProgressLabel());
+        assertEquals(base.getConfirmLabel(), compat.getConfirmLabel());
+        assertEquals(base.getCancelLabel(), compat.getCancelLabel());
+    }
+
+    // Parse the Notification using the base parser and the compat parser and confirm
+    // that the WearableExtender bundles are equivelent.
+    private void assertExtendersFromNotificationEqual(Notification first,
+                                                      Notification second) {
+        Notification.WearableExtender baseExtender = new Notification.WearableExtender(first);
+        NotificationCompat.WearableExtender compatExtender =
+            new NotificationCompat.WearableExtender(second);
+        List<Notification.Action> baseArray = baseExtender.getActions();
+        List<NotificationCompat.Action> compatArray = compatExtender.getActions();
+        assertEquals(baseArray.size(), compatArray.size());
+        for (int i = 0; i < baseArray.size(); i++) {
+            // Verify that the key value pairs are equal. We only care about
+            // the bundle in getExtras().getBundle("android.wearable.EXTENSIONS"),
+            // but it doesn't hurt to check them all as long we recurse.
+            assertBundlesEqual(baseArray.get(i).getExtras(),
+                               compatArray.get(i).getExtras());
+            // Verify that the parsed WearableExtentions are equal
+            Notification.Action.WearableExtender base =
+                new Notification.Action.WearableExtender(baseArray.get(i));
+            NotificationCompat.Action.WearableExtender compat =
+                new NotificationCompat.Action.WearableExtender(compatArray.get(i));
+            assertExtendersEqual(base, compat);
+        }
+    }
+
+    private void assertBundlesEqual(Bundle bundle1, Bundle bundle2) {
+        assertEquals(bundle1.size(), bundle2.size());
+        for (String key : bundle1.keySet()) {
+            Object value1 = bundle1.get(key);
+            Object value2 = bundle2.get(key);
+            if (value1 instanceof Bundle && value2 instanceof Bundle) {
+                assertBundlesEqual((Bundle) value1, (Bundle) value2);
+            } else {
+                assertEquals(value1, value2);
+            }
+        }
+    }
+}
diff --git a/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
index c6fa124..2a988ed 100644
--- a/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
+++ b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
@@ -84,7 +84,10 @@
                 R.drawable.action_icon, "Test title", mPendingIntent)
                 .addRemoteInput(remoteInput.build())
                 .extend(new NotificationCompat.Action.WearableExtender()
-                        .setAvailableOffline(false));
+                        .setAvailableOffline(false)
+                        .setInProgressLabel("In Progress Label")
+                        .setConfirmLabel("Confirmation Label")
+                        .setCancelLabel("Cancelation Label"));
         // Add an arbitrary key/value.
         action2.getExtras().putFloat("action_float", 10.5f);
 
@@ -129,7 +132,10 @@
                 R.drawable.action_icon, "Test title", mPendingIntent)
                 .addRemoteInput(remoteInput.build())
                 .extend(new Notification.Action.WearableExtender()
-                        .setAvailableOffline(false));
+                        .setAvailableOffline(false)
+                        .setInProgressLabel("In Progress Label")
+                        .setConfirmLabel("Confirmation Label")
+                        .setCancelLabel("Cancelation Label"));
         // Add an arbitrary key/value.
         action2.getExtras().putFloat("action_float", 10.5f);
 
@@ -242,7 +248,13 @@
     private void assertBundlesEqual(Bundle bundle1, Bundle bundle2) {
         assertEquals(bundle1.size(), bundle2.size());
         for (String key : bundle1.keySet()) {
-            assertEquals(bundle1.get(key), bundle2.get(key));
+            Object value1 = bundle1.get(key);
+            Object value2 = bundle2.get(key);
+            if (value1 instanceof Bundle && value2 instanceof Bundle) {
+                assertBundlesEqual((Bundle) value1, (Bundle) value2);
+            } else {
+                assertEquals(value1, value2);
+            }
         }
     }
 }
diff --git a/tests/java/android/support/v4/content/FileProviderTest.java b/tests/java/android/support/v4/content/FileProviderTest.java
index b7bd01c..f8122fa 100644
--- a/tests/java/android/support/v4/content/FileProviderTest.java
+++ b/tests/java/android/support/v4/content/FileProviderTest.java
@@ -26,6 +26,10 @@
 import android.support.v4.content.FileProvider.SimplePathStrategy;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.Suppress;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -33,12 +37,10 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
 /**
  * Tests for {@link FileProvider}
  */
+@Suppress
 public class FileProviderTest extends AndroidTestCase {
     private static final String TEST_AUTHORITY = "moocow";
 
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index f64ca8e..1574cb7 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -43,7 +43,7 @@
 #  A helper sub-library that makes direct use of API 21.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v17-leanback-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -93,6 +93,7 @@
     $(call all-java-files-under, src) \
     $(call all-html-files-under, src)
 leanback.docs.java_libraries := \
+    framework \
     android-support-v4 \
     android-support-v7-recyclerview \
     android-support-v17-leanback-res \
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java b/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
new file mode 100644
index 0000000..469847f
--- /dev/null
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.content.Context;
+import android.transition.Slide;
+import android.util.AttributeSet;
+
+public class SlideNoPropagation extends Slide {
+
+    public SlideNoPropagation() {
+    }
+
+    public SlideNoPropagation(int slideEdge) {
+        super(slideEdge);
+    }
+
+    public SlideNoPropagation(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void setSlideEdge(int slideEdge) {
+        super.setSlideEdge(slideEdge);
+        setPropagation(null);
+    }
+}
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
index 44c88d8..4de735a 100644
--- a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -52,6 +52,7 @@
             shadowContainer.setOutlineProvider(sOutlineProvider);
         }
         shadowContainer.setZ(sNormalZ);
+        shadowContainer.setTransitionGroup(true);
         return shadowContainer;
     }
 
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java b/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
deleted file mode 100644
index 30d258a..0000000
--- a/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.transition;
-
-import android.view.View;
-
-/**
- * Used by Slide to determine the slide edge and distance when it is about to
- * create animator.
- * @hide
- */
-public interface SlideCallback {
-
-    /**
-     * Called when Slide is about to create animator for an appearing/disappearing view.
-     * Callback returns true to ask Slide to create animator, edge is returned
-     * in edge[0], distance in pixels is returned in distance[0].  Slide will not
-     * create animator if callback returns false.
-     */
-    public boolean getSlide(View view, boolean appear, int[] edge, float[] distance);
-
-}
diff --git a/v17/leanback/generatev4.py b/v17/leanback/generatev4.py
new file mode 100755
index 0000000..58a727a
--- /dev/null
+++ b/v17/leanback/generatev4.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+print "Generate v4 fragment related code for leanback"
+
+cls = ['Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
+      'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid']
+
+for w in cls:
+    print "copy {}Fragment to {}SupportFragment".format(w, w)
+
+    file = open('src/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'r')
+    outfile = open('src/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'w')
+
+    outfile.write("/* This file is auto-generated from {}Fragment.java.  DO NOT MODIFY. */\n\n".format(w))
+
+    for line in file:
+        for w in cls:
+            line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
+        line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
+        line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
+        outfile.write(line)
+    file.close()
+    outfile.close()
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java
similarity index 61%
copy from v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
copy to v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java
index 5ebd187..70b8ce9 100644
--- a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
+++ b/v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java
@@ -11,20 +11,19 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
+package android.support.v17.leanback.os;
 
-package android.support.v4.view;
+import android.os.Trace;
 
-import android.view.ViewGroup;
+class TraceHelperJbmr2 {
 
-class ViewGroupCompatApi21 {
-
-    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-        group.setTransitionGroup(isTransitionGroup);
+    public static void beginSection(String section) {
+        Trace.beginSection(section);
     }
 
-    public static boolean isTransitionGroup(ViewGroup group) {
-        return group.isTransitionGroup();
+    public static void endSection() {
+        Trace.endSection();
     }
 }
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
new file mode 100644
index 0000000..0cc9081
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.view.Gravity;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+class LeanbackTransitionHelperKitKat {
+
+    static public Object loadTitleInTransition(Context context) {
+        SlideKitkat slide = new SlideKitkat();
+        slide.setSlideEdge(Gravity.TOP);
+        slide.setInterpolator(AnimationUtils.loadInterpolator(context,
+                android.R.anim.decelerate_interpolator));
+        slide.addTarget(R.id.browse_title_group);
+        return slide;
+    }
+
+    static public Object loadTitleOutTransition(Context context) {
+        SlideKitkat slide = new SlideKitkat();
+        slide.setSlideEdge(Gravity.TOP);
+        slide.setInterpolator(AnimationUtils.loadInterpolator(context,
+                R.animator.lb_decelerator_4));
+        slide.addTarget(R.id.browse_title_group);
+        return slide;
+    }
+
+}
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
index 2bdc3aa..28acbbd 100644
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
@@ -22,6 +22,9 @@
 import android.transition.Transition;
 import android.transition.TransitionValues;
 
+/**
+ * @hide
+ */
 class Scale extends Transition {
     private static final String PROPNAME_SCALE = "android:leanback:scale";
 
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
deleted file mode 100644
index 82c72cf..0000000
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.transition;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.transition.Visibility;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.support.v17.leanback.R;
-
-/**
- * Slide distance toward/from a edge.  The direction and distance are determined by
- * {@link SlideCallback}.
- */
-class Slide extends Visibility {
-    private static final String TAG = "Slide";
-
-    /**
-     * Move Views in or out of the left edge of the scene.
-     * @see #setSlideEdge(int)
-     */
-    public static final int LEFT = 0;
-
-    /**
-     * Move Views in or out of the top edge of the scene.
-     * @see #setSlideEdge(int)
-     */
-    public static final int TOP = 1;
-
-    /**
-     * Move Views in or out of the right edge of the scene.
-     * @see #setSlideEdge(int)
-     */
-    public static final int RIGHT = 2;
-
-    /**
-     * Move Views in or out of the bottom edge of the scene. This is the
-     * default slide direction.
-     * @see #setSlideEdge(int)
-     */
-    public static final int BOTTOM = 3;
-
-    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
-    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
-
-    private int[] mTempLoc = new int[2];
-    SlideCallback mCallback;
-    private int[] mTempEdge = new int[1];
-    private float[] mTempDistance = new float[1];
-
-    private interface CalculateSlide {
-        /** Returns the translation value for view when it out of the scene */
-        float getGone(float slide, View view);
-
-        /** Returns the translation value for view when it is in the scene */
-        float getHere(View view);
-
-        /** Returns the property to animate translation */
-        Property<View, Float> getProperty();
-    }
-
-    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
-        @Override
-        public float getHere(View view) {
-            return view.getTranslationX();
-        }
-
-        @Override
-        public Property<View, Float> getProperty() {
-            return View.TRANSLATION_X;
-        }
-    }
-
-    private static abstract class CalculateSlideVertical implements CalculateSlide {
-        @Override
-        public float getHere(View view) {
-            return view.getTranslationY();
-        }
-
-        @Override
-        public Property<View, Float> getProperty() {
-            return View.TRANSLATION_Y;
-        }
-    }
-
-    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
-        @Override
-        public float getGone(float distance, View view) {
-            return view.getTranslationX() - distance;
-        }
-    };
-
-    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
-        @Override
-        public float getGone(float distance, View view) {
-            return view.getTranslationY() - distance;
-        }
-    };
-
-    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
-        @Override
-        public float getGone(float distance, View view) {
-            return view.getTranslationX() + distance;
-        }
-    };
-
-    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
-        @Override
-        public float getGone(float distance, View view) {
-            return view.getTranslationY() + distance;
-        }
-    };
-
-    public Slide() {
-    }
-
-    public void setCallback(SlideCallback callback) {
-        mCallback = callback;
-    }
-
-    private CalculateSlide getSlideEdge(int slideEdge) {
-        switch (slideEdge) {
-            case LEFT:
-                return sCalculateLeft;
-            case TOP:
-                return sCalculateTop;
-            case RIGHT:
-                return sCalculateRight;
-            case BOTTOM:
-                return sCalculateBottom;
-            default:
-                throw new IllegalArgumentException("Invalid slide direction");
-        }
-    }
-
-    private Animator createAnimation(final View view, Property<View, Float> property,
-            float start, float end, float terminalValue, TimeInterpolator interpolator,
-            int finalVisibility) {
-        float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
-        if (startPosition != null) {
-            start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
-            view.setTag(R.id.lb_slide_transition_value, null);
-        }
-        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
-
-        SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
-                finalVisibility);
-        anim.addListener(listener);
-        anim.addPauseListener(listener);
-        anim.setInterpolator(interpolator);
-        return anim;
-    }
-
-    @Override
-    public Animator onAppear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
-        View view = (endValues != null) ? endValues.view : null;
-        if (view == null) {
-            return null;
-        }
-        if (mCallback == null || !mCallback.getSlide(view, true, mTempEdge, mTempDistance)) {
-            return null;
-        }
-        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
-        float end = slideCalculator.getHere(view);
-        float start = slideCalculator.getGone(mTempDistance[0], view);
-        return createAnimation(view, slideCalculator.getProperty(), start, end, end, sDecelerate,
-                View.VISIBLE);
-    }
-
-    @Override
-    public Animator onDisappear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
-        View view = (startValues != null) ? startValues.view : null;
-        if (view == null) {
-            return null;
-        }
-        if (mCallback == null || !mCallback.getSlide(view, false, mTempEdge, mTempDistance)) {
-            return null;
-        }
-        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
-        float start = slideCalculator.getHere(view);
-        float end = slideCalculator.getGone(mTempDistance[0], view);
-
-        return createAnimation(view, slideCalculator.getProperty(), start, end, start,
-                sAccelerate, View.INVISIBLE);
-    }
-
-    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
-        private boolean mCanceled = false;
-        private float mPausedValue;
-        private final View mView;
-        private final float mEndValue;
-        private final float mTerminalValue;
-        private final int mFinalVisibility;
-        private final Property<View, Float> mProp;
-
-        public SlideAnimatorListener(View view, Property<View, Float> prop,
-                float terminalValue, float endValue, int finalVisibility) {
-            mProp = prop;
-            mView = view;
-            mTerminalValue = terminalValue;
-            mEndValue = endValue;
-            mFinalVisibility = finalVisibility;
-            view.setVisibility(View.VISIBLE);
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animator) {
-            float[] transitionPosition = new float[2];
-            transitionPosition[0] = mView.getTranslationX();
-            transitionPosition[1] = mView.getTranslationY();
-            mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
-            mProp.set(mView, mTerminalValue);
-            mCanceled = true;
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animator) {
-            if (!mCanceled) {
-                mProp.set(mView, mTerminalValue);
-            }
-            mView.setVisibility(mFinalVisibility);
-        }
-
-        @Override
-        public void onAnimationPause(Animator animator) {
-            mPausedValue = mProp.get(mView);
-            mProp.set(mView, mEndValue);
-            mView.setVisibility(mFinalVisibility);
-        }
-
-        @Override
-        public void onAnimationResume(Animator animator) {
-            mProp.set(mView, mPausedValue);
-            mView.setVisibility(View.VISIBLE);
-        }
-    }
-}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
new file mode 100644
index 0000000..a1f2d63
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.transition.Visibility;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+import android.support.v17.leanback.R;
+
+/**
+ * Slide distance toward/from a edge.
+ * This is a limited Slide implementation for KitKat without propagation support.
+ * @hide
+ */
+class SlideKitkat extends Visibility {
+    private static final String TAG = "SlideKitkat";
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+    private int mSlideEdge;
+    private CalculateSlide mSlideCalculator;
+
+    private interface CalculateSlide {
+        /** Returns the translation value for view when it out of the scene */
+        float getGone(View view);
+
+        /** Returns the translation value for view when it is in the scene */
+        float getHere(View view);
+
+        /** Returns the property to animate translation */
+        Property<View, Float> getProperty();
+    }
+
+    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationX();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_X;
+        }
+    }
+
+    private static abstract class CalculateSlideVertical implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationY();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_Y;
+        }
+    }
+
+    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(View view) {
+            return view.getTranslationX() - view.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+        @Override
+        public float getGone(View view) {
+            return view.getTranslationY() - view.getHeight();
+        }
+    };
+
+    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(View view) {
+            return view.getTranslationX() + view.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+        @Override
+        public float getGone(View view) {
+            return view.getTranslationY() + view.getHeight();
+        }
+    };
+
+    private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(View view) {
+            if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+                return view.getTranslationX() + view.getWidth();
+            } else {
+                return view.getTranslationX() - view.getWidth();
+            }
+        }
+    };
+
+    private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(View view) {
+            if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+                return view.getTranslationX() - view.getWidth();
+            } else {
+                return view.getTranslationX() + view.getWidth();
+            }
+        }
+    };
+
+    public SlideKitkat() {
+        setSlideEdge(Gravity.BOTTOM);
+    }
+
+    public SlideKitkat(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
+        int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.BOTTOM);
+        setSlideEdge(edge);
+        long duration = a.getInt(R.styleable.lbSlide_android_duration, -1);
+        if (duration >= 0) {
+            setDuration(duration);
+        }
+        long startDelay = a.getInt(R.styleable.lbSlide_android_startDelay, -1);
+        if (startDelay > 0) {
+            setStartDelay(startDelay);
+        }
+        final int resID = a.getResourceId(R.styleable.lbSlide_android_interpolator, 0);
+        if (resID > 0) {
+            setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+        }
+        a.recycle();
+    }
+
+    /**
+     * Change the edge that Views appear and disappear from.
+     *
+     * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
+     *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+     *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+     *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+     */
+    public void setSlideEdge(int slideEdge) {
+        switch (slideEdge) {
+            case Gravity.LEFT:
+                mSlideCalculator = sCalculateLeft;
+                break;
+            case Gravity.TOP:
+                mSlideCalculator = sCalculateTop;
+                break;
+            case Gravity.RIGHT:
+                mSlideCalculator = sCalculateRight;
+                break;
+            case Gravity.BOTTOM:
+                mSlideCalculator = sCalculateBottom;
+                break;
+            case Gravity.START:
+                mSlideCalculator = sCalculateStart;
+                break;
+            case Gravity.END:
+                mSlideCalculator = sCalculateEnd;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid slide direction");
+        }
+        mSlideEdge = slideEdge;
+    }
+
+    /**
+     * Returns the edge that Views appear and disappear from.
+     * @return the edge of the scene to use for Views appearing and disappearing. One of
+     *         {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+     *         {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+     *         {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+     */
+    public int getSlideEdge() {
+        return mSlideEdge;
+    }
+
+    private Animator createAnimation(final View view, Property<View, Float> property,
+            float start, float end, float terminalValue, TimeInterpolator interpolator,
+            int finalVisibility) {
+        float[] startPosition = (float[]) view.getTag(R.id.lb_slide_transition_value);
+        if (startPosition != null) {
+            start = View.TRANSLATION_Y == property ? startPosition[1] : startPosition[0];
+            view.setTag(R.id.lb_slide_transition_value, null);
+        }
+        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+        SlideAnimatorListener listener = new SlideAnimatorListener(view, property, terminalValue, end,
+                finalVisibility);
+        anim.addListener(listener);
+        anim.addPauseListener(listener);
+        anim.setInterpolator(interpolator);
+        return anim;
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View view = (endValues != null) ? endValues.view : null;
+        if (view == null) {
+            return null;
+        }
+        float end = mSlideCalculator.getHere(view);
+        float start = mSlideCalculator.getGone(view);
+        return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate,
+                View.VISIBLE);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View view = (startValues != null) ? startValues.view : null;
+        if (view == null) {
+            return null;
+        }
+        float start = mSlideCalculator.getHere(view);
+        float end = mSlideCalculator.getGone(view);
+
+        return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
+                sAccelerate, View.INVISIBLE);
+    }
+
+    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+        private boolean mCanceled = false;
+        private float mPausedValue;
+        private final View mView;
+        private final float mEndValue;
+        private final float mTerminalValue;
+        private final int mFinalVisibility;
+        private final Property<View, Float> mProp;
+
+        public SlideAnimatorListener(View view, Property<View, Float> prop,
+                float terminalValue, float endValue, int finalVisibility) {
+            mProp = prop;
+            mView = view;
+            mTerminalValue = terminalValue;
+            mEndValue = endValue;
+            mFinalVisibility = finalVisibility;
+            view.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animator) {
+            float[] transitionPosition = new float[2];
+            transitionPosition[0] = mView.getTranslationX();
+            transitionPosition[1] = mView.getTranslationY();
+            mView.setTag(R.id.lb_slide_transition_value, transitionPosition);
+            mProp.set(mView, mTerminalValue);
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            if (!mCanceled) {
+                mProp.set(mView, mTerminalValue);
+            }
+            mView.setVisibility(mFinalVisibility);
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedValue = mProp.get(mView);
+            mProp.set(mView, mEndValue);
+            mView.setVisibility(mFinalVisibility);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mProp.set(mView, mPausedValue);
+            mView.setVisibility(View.VISIBLE);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
index 43ea462..b4b6abe 100644
--- a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
@@ -21,6 +21,7 @@
 import android.transition.Fade;
 import android.transition.Scene;
 import android.transition.Transition;
+import android.transition.TransitionInflater;
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
 import android.transition.TransitionValues;
@@ -58,9 +59,9 @@
         return new AutoTransition();
     }
 
-    static Object createSlide(SlideCallback callback) {
-        Slide slide = new Slide();
-        slide.setCallback(callback);
+    static Object createSlide(int slideEdge) {
+        SlideKitkat slide = new SlideKitkat();
+        slide.setSlideEdge(slideEdge);
         return slide;
     }
 
@@ -223,4 +224,8 @@
     static void addTarget(Object transition, View view) {
         ((Transition) transition).addTarget(view);
     }
+
+    static Object loadTransition(Context context, int resId) {
+        return TransitionInflater.from(context).inflateTransition(resId);
+    }
 }
diff --git a/v17/leanback/res/animator/lb_decelerator_4.xml b/v17/leanback/res/animator/lb_decelerator_4.xml
new file mode 100644
index 0000000..6273eef
--- /dev/null
+++ b/v17/leanback/res/animator/lb_decelerator_4.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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="4.0"/>
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
index 112b541..3058076 100644
--- a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
index 7c59b7f..653419e 100644
--- a/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
index 4abb20a..9780ed2 100644
--- a/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
index e737a661..b4c0abe 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
index 542dd87..283b4d8 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
index 222b514..f45f1fd 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
index 5db5420..25617f5 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
index 78e9b1e..2eaecbd 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
index d5c5dc1..e27282e 100644
--- a/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
index 562ae9d..e296282 100644
--- a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
index db44754..df53d0d 100644
--- a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
index 1d2b041..823c69c8 100644
--- a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
index 39b220c..2112154 100644
--- a/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
index b9c3400..8252ee4 100644
--- a/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
index aa6b44f..8c2c3b9 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
index ffdc8ec..9fd5012 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
index d2ec22a..b0bed22 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
index 7cc6d61..75eb962 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
index 9e36b2e..1682a46 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
index be062cc..fdf205e 100644
--- a/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
index b63a57f..8ef4ae9 100644
--- a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
index d91acee..36ea129 100644
--- a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
index 5e7c2be..cafe2ab 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
index 599928b..635fa5c 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
index e5413a8..060d56f 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
index 9796171..d0ca2e2 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
index 1364a48..a09fdee 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
index 518c063..d4616cf 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
index c5afb69..0dfefcc 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
index d9774ef..09e8a3b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
index f797d38..5aefe6d 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
index 1dd0d0f..8ef325b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
index 988f572..791386e 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
index 4ae9faa..e10f5d3 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
index ef62a69..662e03c 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
index 01e07f7..e55f78d 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
index 1556076..fbd792b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
index f444ca4..e5c9acc 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
index a76d8fe..c5a0294 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
index b048503..93a74f6 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
index a99d693..0785c8b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
index f8b3cca..7bbfa23 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
index 32cf2dc..5aa850b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
index 2173375..7349a07 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
index 2c6035a..a27e543 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
index b96f297..586c4bd 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
index 85dd902..6e9d472 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
index 2208fdf..6000fa3 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
index b24b146..54b9ad4 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
index d6a5240..7a9706e 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
index 8cc5438..894ab76 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
index f913c0f..125d6d1 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
index 791ffd7..5204234 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png b/v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png
index bdc82af..af12c1d 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png b/v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
index 80ed1c2..219bfca 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png
index deec9bb..7861019 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
index b22cb9c..65f522c 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
index d2227b9..1bef6f2 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
index 125bf12..3f1affa 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
index 887d24f..921688a 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
index 6539869..42b7c77 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
index f47827f..b45deb6 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
index 825c693..08fd07c 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
index c8a0790..a36a912 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
index cb97131..8c251e1 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
index b9e372e..fef8b07 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
index 65f3a9e..ceb6a40 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
index 9bc3f6f..18d2fcb 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
index 8ea9a53..bc8ffc3 100644
--- a/v17/leanback/res/layout/lb_browse_fragment.xml
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -21,7 +21,7 @@
 
     <!-- BrowseFrameLayout serves as root of transition and manages switch between
          left and right-->
-    <android.support.v17.leanback.app.BrowseFrameLayout
+    <android.support.v17.leanback.widget.BrowseFrameLayout
         android:focusable="true"
         android:focusableInTouchMode="true"
         android:descendantFocusability="afterDescendants"
@@ -29,11 +29,10 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         >
-        <android.support.v17.leanback.app.BrowseRowsFrameLayout
+        <android.support.v17.leanback.widget.BrowseRowsFrameLayout
             android:id="@+id/browse_container_dock"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:clipChildren="false" />
+            android:layout_height="match_parent" />
 
         <!-- Padding needed for shadow -->
         <FrameLayout
@@ -43,5 +42,5 @@
             android:clipToPadding="false"
             android:paddingEnd="50dp" />
         <include layout="@layout/lb_browse_title" />
-    </android.support.v17.leanback.app.BrowseFrameLayout>
+    </android.support.v17.leanback.widget.BrowseFrameLayout>
 </FrameLayout>
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/v17/leanback/res/layout/lb_browse_title.xml
index 75068d8..e14350a 100644
--- a/v17/leanback/res/layout/lb_browse_title.xml
+++ b/v17/leanback/res/layout/lb_browse_title.xml
@@ -19,8 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingTop="?attr/browsePaddingTop"
-    android:paddingRight="?attr/browsePaddingRight"
-    android:paddingLeft="?attr/browsePaddingLeft"
+    android:paddingEnd="?attr/browsePaddingEnd"
+    android:paddingStart="?attr/browsePaddingStart"
     android:paddingBottom="?attr/browsePaddingTop"
     style="?attr/browseTitleViewStyle" />
 
diff --git a/v17/leanback/res/layout/lb_control_button_primary.xml b/v17/leanback/res/layout/lb_control_button_primary.xml
index 86e3e0d..af94487 100644
--- a/v17/leanback/res/layout/lb_control_button_primary.xml
+++ b/v17/leanback/res/layout/lb_control_button_primary.xml
@@ -33,4 +33,11 @@
         android:layout_height="@dimen/lb_control_icon_height"
         android:layout_gravity="center" />
 
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        style="?attr/playbackControlButtonLabelStyle" />
+
 </FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_details_description.xml b/v17/leanback/res/layout/lb_details_description.xml
index 798504f..005cae4 100644
--- a/v17/leanback/res/layout/lb_details_description.xml
+++ b/v17/leanback/res/layout/lb_details_description.xml
@@ -23,7 +23,7 @@
     >
 
     <!-- Top margins set programatically -->
-    <TextView
+    <android.support.v17.leanback.widget.ResizingTextView
         android:id="@+id/lb_details_description_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/v17/leanback/res/layout/lb_details_overview.xml
index 1e4e0fc..dd73e76 100644
--- a/v17/leanback/res/layout/lb_details_overview.xml
+++ b/v17/leanback/res/layout/lb_details_overview.xml
@@ -19,8 +19,8 @@
     xmlns:lb="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingLeft="@dimen/lb_details_overview_margin_left"
-    android:paddingRight="@dimen/lb_details_overview_margin_right"
+    android:paddingStart="@dimen/lb_details_overview_margin_start"
+    android:paddingEnd="@dimen/lb_details_overview_margin_end"
     android:paddingBottom="@dimen/lb_details_overview_margin_bottom"
     android:clipToPadding="false"
     >
@@ -59,12 +59,12 @@
 
         <android.support.v17.leanback.widget.NonOverlappingFrameLayout
             android:id="@+id/details_overview_description"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"
             android:gravity="top"
-            android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
-            android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
+            android:layout_marginStart="@dimen/lb_details_overview_description_margin_start"
+            android:layout_marginEnd="@dimen/lb_details_overview_description_margin_end"
             android:paddingTop="@dimen/lb_details_overview_description_margin_top"
             android:clipToPadding="false"
             android:clipChildren="false"
@@ -79,8 +79,8 @@
             android:clipToPadding="false"
             android:focusable="true"
             android:focusableInTouchMode="true"
-            android:paddingLeft="@dimen/lb_details_overview_description_margin_left"
-            android:paddingRight="@dimen/lb_details_overview_description_margin_right"
+            android:paddingStart="@dimen/lb_details_overview_description_margin_start"
+            android:paddingEnd="@dimen/lb_details_overview_description_margin_end"
             lb:horizontalMargin="@dimen/lb_details_overview_action_items_margin"
             lb:rowHeight="@dimen/lb_details_overview_actions_height" />
 
diff --git a/v17/leanback/res/layout/lb_headers_fragment.xml b/v17/leanback/res/layout/lb_headers_fragment.xml
index a621c0b..035b116 100644
--- a/v17/leanback/res/layout/lb_headers_fragment.xml
+++ b/v17/leanback/res/layout/lb_headers_fragment.xml
@@ -16,9 +16,11 @@
 -->
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_headers_root"
+    android:transitionGroup="true"
     android:layout_width="@dimen/lb_browse_headers_width"
     android:layout_height="match_parent"
-    android:paddingRight="@dimen/lb_browse_header_padding_right"
+    android:paddingEnd="@dimen/lb_browse_header_padding_end"
     android:elevation="@dimen/lb_browse_headers_z"
     >
     <android.support.v17.leanback.widget.VerticalGridView
@@ -28,7 +30,7 @@
         style="?attr/headersVerticalGridStyle"/>
     <View
         android:id="@+id/fade_out_edge"
-        android:layout_alignParentRight="true"
+        android:layout_alignParentEnd="true"
         android:layout_width="@dimen/lb_browse_header_fading_length"
         android:layout_height="match_parent"
         android:background="@drawable/lb_headers_right_fading" >
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
index f74085c3..38cfd4b 100644
--- a/v17/leanback/res/layout/lb_image_card_view.xml
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -43,7 +43,7 @@
                 android:layout_height="wrap_content"
                 android:layout_alignParentStart="true"
                 android:layout_marginTop="@dimen/lb_basic_card_info_text_margin"
-                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginStart="@dimen/lb_basic_card_info_text_margin"
                 android:maxLines="1"
                 android:fontFamily="sans-serif-condensed"
                 android:textColor="@color/lb_basic_card_title_text_color"
@@ -53,9 +53,9 @@
                 android:id="@+id/content_text"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
                 android:layout_alignParentBottom="true"
-                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginStart="@dimen/lb_basic_card_info_text_margin"
                 android:layout_marginBottom="@dimen/lb_basic_card_info_text_margin"
                 android:maxLines="1"
                 android:fontFamily="sans-serif-condensed"
@@ -67,7 +67,7 @@
                 android:layout_width="@dimen/lb_basic_card_info_badge_size"
                 android:layout_height="@dimen/lb_basic_card_info_badge_size"
                 android:layout_alignParentBottom="true"
-                android:layout_alignParentRight="true"
+                android:layout_alignParentEnd="true"
                 android:scaleType="fitCenter"
                 android:visibility="gone"
                 android:contentDescription="@null" />
diff --git a/v17/leanback/res/layout/lb_playback_controls.xml b/v17/leanback/res/layout/lb_playback_controls.xml
index e7ed643..b366215 100644
--- a/v17/leanback/res/layout/lb_playback_controls.xml
+++ b/v17/leanback/res/layout/lb_playback_controls.xml
@@ -27,6 +27,7 @@
         android:layout_height="4dp"
         android:maxHeight="4dp"
         android:minHeight="4dp"
+        android:layoutDirection="ltr"
         android:progressDrawable="@drawable/lb_playback_progress_bar" />
 
     <FrameLayout
@@ -39,19 +40,20 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
+            android:layoutDirection="ltr"
             android:orientation="horizontal" />
 
         <FrameLayout
             android:id="@+id/more_actions_dock"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="right" />
+            android:layout_gravity="end" />
 
         <TextView
             android:id="@+id/current_time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="top|start"
+            android:layout_gravity="top|left"
             android:layout_marginStart="@dimen/lb_playback_current_time_margin_start"
             android:paddingTop="@dimen/lb_playback_time_padding_top"
             style="?attr/playbackControlsTimeStyle" />
@@ -60,7 +62,7 @@
             android:id="@+id/total_time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="top|end"
+            android:layout_gravity="top|right"
             android:layout_marginEnd="@dimen/lb_playback_total_time_margin_end"
             android:paddingTop="@dimen/lb_playback_time_padding_top"
             style="?attr/playbackControlsTimeStyle" />
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
index 2875b51..24b367e 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -16,16 +16,16 @@
 -->
 
 <!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
-     views with no padding of their own. -->
+     views with no padding of their own. Also to allow for negative marge on description. -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v17.leanback.widget.PlaybackControlsRowView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingLeft="@dimen/lb_playback_controls_margin_left"
-    android:paddingRight="@dimen/lb_playback_controls_margin_right" >
+    android:paddingStart="@dimen/lb_playback_controls_margin_start"
+    android:paddingEnd="@dimen/lb_playback_controls_margin_end" >
 
     <LinearLayout
         android:id="@+id/controls_card"
@@ -56,7 +56,9 @@
                 android:layout_height="0dp"
                 android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
                 android:layout_marginStart="@dimen/lb_playback_description_margin_start"
-                android:layout_marginTop="@dimen/lb_playback_description_margin_top"
+                android:paddingTop="@dimen/lb_playback_description_margin_top"
+                android:clipToPadding="false"
+                android:clipChildren="false"
                 android:layout_weight="1"
                 android:gravity="top" />
 
@@ -71,6 +73,7 @@
                 android:id="@+id/controls_dock"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:minHeight="@dimen/lb_control_button_height"
                 android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
                 android:layout_marginStart="@dimen/lb_playback_description_margin_start" />
         </LinearLayout>
@@ -87,4 +90,4 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/lb_playback_controls_margin_bottom" />
 
-</LinearLayout>
\ No newline at end of file
+</android.support.v17.leanback.widget.PlaybackControlsRowView>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_container.xml b/v17/leanback/res/layout/lb_row_container.xml
index 1ed9f83..69fb0e1 100644
--- a/v17/leanback/res/layout/lb_row_container.xml
+++ b/v17/leanback/res/layout/lb_row_container.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
-    android:paddingLeft="?attr/browsePaddingLeft"
+    android:paddingStart="?attr/browsePaddingStart"
     android:clipToPadding="false">
 </android.support.v17.leanback.widget.NonOverlappingLinearLayout>
 </merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
index c4ffdc3..c188b3c 100644
--- a/v17/leanback/res/layout/lb_rows_fragment.xml
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -14,10 +14,16 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<android.support.v17.leanback.widget.VerticalGridView
+<android.support.v17.leanback.widget.ScaleFrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container_list"
+    android:id="@+id/scale_frame"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    style="?attr/rowsVerticalGridStyle" />
+    android:layout_height="match_parent">
 
+    <android.support.v17.leanback.widget.VerticalGridView
+        android:id="@+id/container_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        style="?attr/rowsVerticalGridStyle" />
+
+</android.support.v17.leanback.widget.ScaleFrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
index 4dff716..6123ea7 100644
--- a/v17/leanback/res/layout/lb_search_bar.xml
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -20,7 +20,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_centerVertical="true"
-            android:layout_marginLeft="@dimen/lb_search_bar_speech_orb_margin_left"
+            android:layout_marginStart="@dimen/lb_search_bar_speech_orb_margin_start"
             android:focusable="true"
             android:focusableInTouchMode="true"
             >
@@ -31,9 +31,9 @@
             android:id="@+id/lb_search_bar_items"
             android:layout_width="@dimen/lb_search_bar_items_width"
             android:layout_height="@dimen/lb_search_bar_items_height"
-            android:layout_toRightOf="@+id/lb_search_bar_speech_orb"
+            android:layout_toEndOf="@+id/lb_search_bar_speech_orb"
             android:layout_centerVertical="true"
-            android:layout_marginLeft="@dimen/lb_search_bar_items_margin_left"
+            android:layout_marginStart="@dimen/lb_search_bar_items_margin_start"
             android:layout_marginTop="@dimen/lb_search_bar_inner_margin_top"
             android:layout_marginBottom="@dimen/lb_search_bar_inner_margin_bottom"
             android:background="@drawable/lb_in_app_search_bg"
@@ -43,9 +43,9 @@
                 android:id="@+id/lb_search_bar_badge"
                 android:layout_width="@dimen/lb_search_bar_icon_width"
                 android:layout_height="@dimen/lb_search_bar_icon_height"
-                android:layout_gravity="center_vertical|left"
+                android:layout_gravity="center_vertical|start"
                 android:layout_centerVertical="true"
-                android:layout_marginLeft="@dimen/lb_search_bar_icon_margin_left"
+                android:layout_marginStart="@dimen/lb_search_bar_icon_margin_start"
                 android:src="@null"
                 android:visibility="gone"
                 style="?attr/browseTitleIconStyle"/>
@@ -54,11 +54,11 @@
                     android:id="@+id/lb_search_text_editor"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_gravity="center_vertical|right"
-                    android:layout_marginLeft="@dimen/lb_search_bar_edit_text_margin_left"
+                    android:layout_gravity="center_vertical|end"
+                    android:layout_marginStart="@dimen/lb_search_bar_edit_text_margin_start"
                     android:layout_centerVertical="true"
                     android:cursorVisible="true"
-                    android:layout_toRightOf="@+id/lb_search_bar_badge"
+                    android:layout_toEndOf="@+id/lb_search_bar_badge"
                     android:editable="true"
                     android:background="@null"
                     android:fontFamily="sans-serif"
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/v17/leanback/res/layout/lb_title_view.xml
index 590a683..e82621e 100644
--- a/v17/leanback/res/layout/lb_title_view.xml
+++ b/v17/leanback/res/layout/lb_title_view.xml
@@ -18,9 +18,11 @@
 
     <ImageView
         android:id="@+id/browse_badge"
-        android:layout_width="@dimen/lb_browse_title_text_width"
-        android:layout_height="@dimen/lb_browse_title_height"
-        android:layout_gravity="center_vertical|right"
+        android:layout_width="wrap_content"
+        android:maxWidth="@dimen/lb_browse_title_icon_max_width"
+        android:adjustViewBounds="true"
+        android:layout_height="@dimen/lb_browse_title_icon_height"
+        android:layout_gravity="center_vertical|end"
         android:src="@null"
         android:visibility="gone"
         style="?attr/browseTitleIconStyle"/>
@@ -29,14 +31,15 @@
         android:id="@+id/browse_title"
         android:layout_width="@dimen/lb_browse_title_text_width"
         android:layout_height="@dimen/lb_browse_title_height"
-        android:layout_gravity="center_vertical|right"
+        android:layout_gravity="center_vertical|end"
         style="?attr/browseTitleTextStyle"/>
 
     <android.support.v17.leanback.widget.SearchOrbView
         android:id="@+id/browse_orb"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:layout_gravity="center_vertical|left"
+        android:transitionGroup="true"
+        android:layout_gravity="center_vertical|start"
         android:visibility="invisible" />
 
 </merge>
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
index db43e22..b287986 100644
--- a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -19,7 +19,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
 
-    <android.support.v17.leanback.app.BrowseFrameLayout
+    <android.support.v17.leanback.widget.BrowseFrameLayout
         android:id="@+id/browse_frame"
         android:focusable="true"
         android:focusableInTouchMode="true"
@@ -34,5 +34,5 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
 
-    </android.support.v17.leanback.app.BrowseFrameLayout>
+    </android.support.v17.leanback.widget.BrowseFrameLayout>
 </FrameLayout>
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_in.xml b/v17/leanback/res/transition-v19/lb_browse_headers_in.xml
new file mode 100644
index 0000000..66e7750
--- /dev/null
+++ b/v17/leanback/res/transition-v19/lb_browse_headers_in.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<transitionSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_browse_headers_transition_duration" >
+    <changeBounds android:startDelay="@integer/lb_browse_headers_transition_delay" />
+    <transition class="android.support.v17.leanback.Scale"
+        android:startDelay="@integer/lb_browse_headers_transition_delay" >
+        <targets>
+            <target android:targetId="@id/container_list" />
+        </targets>
+    </transition>
+    <fade android:fadingMode="fade_in"
+        android:startDelay="@integer/lb_browse_headers_transition_delay" />
+    <fade android:fadingMode="fade_out" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_out.xml b/v17/leanback/res/transition-v19/lb_browse_headers_out.xml
new file mode 100644
index 0000000..a12c9b7
--- /dev/null
+++ b/v17/leanback/res/transition-v19/lb_browse_headers_out.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<transitionSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_browse_headers_transition_duration" >
+    <changeBounds />
+    <transition class="android.support.v17.leanback.Scale" >
+        <targets>
+            <target android:targetId="@id/container_list" />
+        </targets>
+    </transition>
+    <fade android:fadingMode="fade_in"
+        android:startDelay="@integer/lb_browse_headers_transition_delay" />
+    <fade android:fadingMode="fade_out" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml b/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml
new file mode 100644
index 0000000..4ad9888
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <fade
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:duration="150"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml b/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml
new file mode 100644
index 0000000..e26204b
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <changeBounds
+      android:duration="350"
+      android:interpolator="@android:interpolator/linear_out_slow_in">
+  </changeBounds>
+  <slide
+      android:duration="350"
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:slideEdge="right">
+  </slide>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_in.xml b/v17/leanback/res/transition-v21/lb_browse_headers_in.xml
new file mode 100644
index 0000000..ee4eaeb
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_headers_in.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<transitionSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_browse_headers_transition_duration" >
+    <changeBounds android:startDelay="@integer/lb_browse_headers_transition_delay"
+        android:interpolator="@android:interpolator/fast_out_linear_in" />
+    <changeTransform
+        android:startDelay="@integer/lb_browse_headers_transition_delay"
+        android:interpolator="@android:interpolator/fast_out_linear_in" >
+        <targets>
+            <target android:targetId="@id/container_list" />
+        </targets>
+    </changeTransform>
+    <fade android:transitionVisibilityMode="mode_in"
+        android:startDelay="@integer/lb_browse_headers_transition_delay"
+        android:interpolator="@android:interpolator/fast_out_linear_in" />
+    <fade android:transitionVisibilityMode="mode_out"
+        android:interpolator="@android:interpolator/fast_out_linear_in" />
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_out.xml b/v17/leanback/res/transition-v21/lb_browse_headers_out.xml
new file mode 100644
index 0000000..667b7ce
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_headers_out.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<transitionSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_browse_headers_transition_duration" >
+    <changeBounds android:interpolator="@android:interpolator/fast_out_linear_in" />
+    <changeTransform android:interpolator="@android:interpolator/fast_out_linear_in" >
+        <targets>
+            <target android:targetId="@id/container_list" />
+        </targets>
+    </changeTransform>
+    <fade android:transitionVisibilityMode="mode_in"
+        android:startDelay="@integer/lb_browse_headers_transition_delay"
+        android:interpolator="@android:interpolator/fast_out_linear_in" />
+    <fade android:transitionVisibilityMode="mode_out"
+        android:interpolator="@android:interpolator/fast_out_linear_in"/>
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_browse_return_transition.xml b/v17/leanback/res/transition-v21/lb_browse_return_transition.xml
new file mode 100644
index 0000000..ee88fff
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_browse_return_transition.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <slide
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350"
+      android:slideEdge="left">
+      <targets>
+          <target android:targetId="@id/browse_headers_root" />
+          <target android:targetId="@id/browse_orb" />
+      </targets>
+  </slide>
+  <slide
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350"
+      android:slideEdge="right">
+      <targets>
+          <target android:excludeId="@+id/browse_headers_root" />
+          <target android:excludeId="@+id/browse_orb" />
+      </targets>
+  </slide>
+  <fade
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350">
+      <targets>
+          <target android:excludeId="@+id/browse_headers_root" />
+      </targets>
+  </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_details_enter_transition.xml b/v17/leanback/res/transition-v21/lb_details_enter_transition.xml
new file mode 100644
index 0000000..240d4bc
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_details_enter_transition.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <transition class="android.support.v17.leanback.transition.SlideNoPropagation"
+      android:duration="500"
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:slideEdge="bottom">
+  </transition>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_details_return_transition.xml b/v17/leanback/res/transition-v21/lb_details_return_transition.xml
new file mode 100644
index 0000000..f555533
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_details_return_transition.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <transition class="android.support.v17.leanback.transition.SlideNoPropagation"
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350"
+      android:slideEdge="bottom">
+  </transition>
+  <fade
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350">
+  </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
index 0ba4125..82913d9 100644
--- a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
+++ b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
@@ -24,8 +24,6 @@
   <changeBounds
       android:interpolator="@android:interpolator/linear_out_slow_in">
   </changeBounds>
-  <changeTransform
-      android:interpolator="@android:interpolator/linear_out_slow_in" />
   <changeImageTransform
       android:interpolator="@android:interpolator/linear_out_slow_in"/>
 </transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_title_in.xml b/v17/leanback/res/transition-v21/lb_title_in.xml
new file mode 100644
index 0000000..7b38785
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_title_in.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<transition
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    class="android.support.v17.leanback.transition.SlideKitkat"
+    android:interpolator="@android:anim/decelerate_interpolator"
+    lb:lb_slideEdge="top" >
+    <targets>
+        <target android:targetId="@id/browse_title_group" />
+    </targets>
+</transition>
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/v17/leanback/res/transition-v21/lb_title_out.xml
new file mode 100644
index 0000000..50fb67e
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_title_out.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<transition
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    class="android.support.v17.leanback.transition.SlideKitkat"
+    android:interpolator="@animator/lb_decelerator_4"
+    lb:lb_slideEdge="top" >
+    <targets>
+        <target android:targetId="@id/browse_title_group" />
+    </targets>
+</transition>
diff --git a/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml b/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml
new file mode 100644
index 0000000..2068c64
--- /dev/null
+++ b/v17/leanback/res/transition-v22/lb_browse_entrance_transition.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <changeBounds
+      android:duration="350"
+      android:interpolator="@android:interpolator/linear_out_slow_in">
+  </changeBounds>
+  <slide
+      android:duration="350"
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:slideEdge="end">
+  </slide>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v22/lb_browse_return_transition.xml b/v17/leanback/res/transition-v22/lb_browse_return_transition.xml
new file mode 100644
index 0000000..62a273e
--- /dev/null
+++ b/v17/leanback/res/transition-v22/lb_browse_return_transition.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <slide
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350"
+      android:slideEdge="start">
+      <targets>
+          <target android:targetId="@id/browse_headers_root" />
+          <target android:targetId="@id/browse_orb" />
+      </targets>
+  </slide>
+  <slide
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350"
+      android:slideEdge="end">
+      <targets>
+          <target android:excludeId="@+id/browse_headers_root" />
+          <target android:excludeId="@+id/browse_orb" />
+      </targets>
+  </slide>
+  <fade
+      android:interpolator="@android:interpolator/fast_out_linear_in"
+      android:duration="350">
+      <targets>
+          <target android:excludeId="@+id/browse_headers_root" />
+      </targets>
+  </fade>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
index 2d570ba..c7109bb 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Praat om te soek"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Deursoek <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Praat om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te deursoek"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Speel"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Laat wag"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vinnig vorentoe"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spoel vorentoe %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spoel terug"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spoel terug %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Slaan volgende oor"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Slaan vorige oor"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Nog handelinge"</string>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index 03a88ce..d5bf0f5 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ለመፈለግ ይናገሩ"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ፈልግ"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ን ለመፈለግ ይናገሩ"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"አጫውት"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"ለአፍታ አቁም"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"በፍጥነት አሳልፍ"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"በ%1$dX ወደፊት አፍጥን"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ወደኋላ አጠንጥን"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"በ%1$dX አጠንጥን"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"የሚቀጥለውን ዝለል"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"የቀደመውን ዝለል"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ተጨማሪ እርምጃዎች"</string>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index 65d854d..31f4d1a 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"التحدث  للبحث"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"بحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"تحدّث للبحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"تشغيل"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"إيقاف مؤقت"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"تقديم سريع"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"‏التقديم السريع %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"إرجاع"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"‏الترجيع %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"تخطي التالي"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"تخطي السابق"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"مزيد من الإجراءات"</string>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index a662ac0..de0b6f8 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорете, за да търсите"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Търсете в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Говорете, за да търсите в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Пускане"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Поставяне на пауза"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Превъртане напред"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Превъртане напред със скорост %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Превъртане назад"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Превъртане назад със скорост %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Напред към следващия елемент"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Назад към предишния елемент"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Още действия"</string>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index b5d9c2e..4f0526c 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"অনুসন্ধান করতে বলুন"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করুন"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করতে বলুন"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"চালান"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"বিরাম দিন"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"দ্রুত ফরওয়ার্ড"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"দ্রুত ফরওয়ার্ড %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"পেছনের দিকে যান"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"পেছনের দিকে যান %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"সরাসরি পরেরটিতে চলে যান"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"সরাসরি আগেরটিতে চলে যান"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"আরো অ্যাকশন"</string>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index 1847873..187f5af 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per fer una cerca."</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercar a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reprodueix"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Posa en pausa"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avança ràpidament"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avança ràpidament %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobina"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobina %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Passa al següent"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Passa a l\'anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Més accions"</string>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index f02aad2..1a608283 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Vyhledávejte hlasem"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hledat <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vyhledávejte v kategorii „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“ hlasem"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d×"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d×"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Přehrát"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pozastavit"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Přetočit vpřed"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Přetočit vpřed %1$d×"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Přetočit zpět"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Přetočit zpět %1$d×"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Přeskočit na další"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Přeskočit na předchozí"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Další akce"</string>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 05a8306..e3e0f9f 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tal for at søge"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søg efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tal for at søge efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Afspil"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sæt på pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Spol frem"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spol frem %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spol tilbage"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spol tilbage %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Spring til næste"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Spring til forrige"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Flere handlinger"</string>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index 52488cf..d729f7c 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Zum Suchen sprechen"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"In <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> suchen"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Zum Suchen in der Kategorie \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\" sprechen"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dx"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dx"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Wiedergabe"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausieren"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vorspulen"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Vorspulen %1$dx"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Zurückspulen"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Zurückspulen %1$dx"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Nächsten Titel überspringen"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Vorherigen Titel überspringen"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Weitere Aktionen"</string>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index fde314c..9b93dcf 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Μιλήστε για να κάνετε αναζήτηση"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Μιλήστε για αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Αναπαραγωγή"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Παύση"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Γρήγορη προώθηση"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Γρήγορη προώθηση %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Επαναφορά"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Επαναφορά %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Παράβλεψη επόμενου"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Παράβλεψη προηγούμενου"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Περισσότερες ενέργειες"</string>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 8e071b3..ed22ccd 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Play"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fast-Forward"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fast Forward %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rewind"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rewind %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Skip Next"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Skip Previous"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"More Actions"</string>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 8e071b3..ed22ccd 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Play"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fast-Forward"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fast Forward %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rewind"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rewind %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Skip Next"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Skip Previous"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"More Actions"</string>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 315ffa0..ab05f83 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Habla para buscar en <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avanzar"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avanzar rápidamente %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retroceder"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinar %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ir al siguiente"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ir al anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Más acciones"</string>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index 4d86408..0cff1c9 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Habla para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rápido"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rápido %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobinar"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinar %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltar siguiente"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltar anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Más acciones"</string>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index 44ecbf5..32fff96 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Öelge otsimiseks"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Otsige teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kõnelge teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> otsimiseks"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Esita"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Peata"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Keri edasi"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Edasikerimine %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Keri tagasi"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tagasikerimine %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Liigu järgmise üksuse juurde"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Liigu eelmise üksuse juurde"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Veel toiminguid"</string>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index 408823b..d9f9bf7 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Esan bilatu nahi duzuna"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Bilatu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Esan bilatu nahi duzuna, bilaketa hemen egiteko: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Erreproduzitu"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausatu"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Aurreratu"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Aurreratu %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Atzeratu"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Atzeratu %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltatu hurrengora"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltatu aurrekora"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Ekintza gehiago"</string>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index 24299df..bb615fc 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"برای جستجو صحبت کنید"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"جستجوی <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"جستجو با گفتن <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"پخش"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"توقف موقت"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"جلو بردن سریع"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"‏بازارسال سریع %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"عقب بردن"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"‏عقب بردن %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"رد شدن از بعدی"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"رد شدن از قبلی"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"عملکردهای بیشتر"</string>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index e62401b..9d38d3c 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tee haku puhumalla"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Haku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Puhehaku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Toista"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Keskeytä"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Kelaa eteenpäin"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Kelaa eteenpäin %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Kelaa taakse"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Kelaa taaksepäin %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Siirry seuraavaan"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Siirry edelliseen"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Lisää toimintoja"</string>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index bdae40e..bbd3eea 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncez votre recherche"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncez votre recherche dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Lecture"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rapide"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rapide à %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Reculer"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retour rapide à %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Passer à l\'élément suivant"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Passer à l\'élément précédent"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Autres actions"</string>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index f3e196b..e9c051c 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncer la recherche"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncer la recherche \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Lecture"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Interrompre"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rapide"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rapide de %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retour arrière"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retour arrière de %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ignorer l\'élément suivant"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ignorer l\'élément précédent"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Autres actions"</string>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index 4720e2d..717b994 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -22,16 +22,20 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fala para efectuar a busca"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Busca <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fala para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproducir"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avance rápido"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avance rápido %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rebobinar"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Rebobinado %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Saltar seguinte"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Saltar anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Máis accións"</string>
-    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Deseleccionar polgar cara arriba"</string>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Anular \"Gústame\""</string>
     <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Seleccionar polgar cara arriba"</string>
-    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Deseleccionar polgar cara abaixo"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Anular \"Non me gusta\""</string>
     <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Seleccionar polgar cara abaixo"</string>
     <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Non repetir"</string>
     <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Repetir todo"</string>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 4ee7c2a..a926396 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजने के लिए बोलें"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजें"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजने के लिए बोलें"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"चलाएं"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"रोकें"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फ़ास्ट फ़ॉरवर्ड"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"फ़ास्‍ट फ़ॉरवर्ड %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"रिवाइंड करें"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"रिवाइंड %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"अगले पर जाएं"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"पिछले पर जाएं"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक विकल्प"</string>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index 196d9bf..166369f 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite upit za pretraživanje"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit za pretraživanje <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduciraj"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauziraj"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Brzo naprijed"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Brzo unaprijed %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Unatrag"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Unatrag %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči na sljedeće"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči na prethodno"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Više radnji"</string>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index 824ec8c..427f1cd 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Beszéljen a keresés indításához"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Keresés itt: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Mondj valamit a kereséshez: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Lejátszás"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Szünet"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Gyors előretekerés"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Előretekerés %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Visszatekerés"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Visszatekerés %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ugrás a következőre"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ugrás az előzőre"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"További műveletek"</string>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 5710fea..7e8112e 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Խոսեք՝ որոնելու համար"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Որոնեք <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Խոսեք՝ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> որոնելու համար"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Նվագարկել"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Դադարեցնել"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Արագ առաջ անցնել"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Առագ առաջանցում %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Հետ փաթաթել"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Հետանցում %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Անցնել հաջորդին"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Անցնել նախորդին"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Այլ գործողություններ"</string>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index 243e354..2dca7d3 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ucapkan untuk menelusuri"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Telusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Ucapkan untuk menelusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Putar"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Jeda"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Maju Cepat"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Maju %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Putar Ulang"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Mundur %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Lewati ke Berikutnya"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Lewati ke Sebelumnya"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Tindakan Lainnya"</string>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index 51ba56e..c84a4c6 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Talaðu til að leita"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Talaðu til að leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Spila"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Hlé"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Spóla áfram"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spóla áfram %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spóla til baka"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spóla til baka %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Fara í næsta"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Fara í fyrra"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Fleiri aðgerðir"</string>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 03933ac0..1b58e0c 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per cercare"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercare in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Riproduci"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Metti in pausa"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avanza velocemente"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avanti veloce: %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Riavvolgi"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Indietro: %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Salta successivo"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Salta precedente"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Altre azioni"</string>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index ca8b42b..f102498 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"חפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"דבר כדי לחפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"‎%1$dX‎‎"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"‎%1$dX‎‎"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"הפעל"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"השהה"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"הרץ קדימה"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"‏העברה קדימה של %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"הרץ אחורה"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"‏העברה לאחור של %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"דלג אל הפריט הבא"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"דלג אל הפריט הקודם"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"עוד פעולות"</string>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 7e63434..802631c 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"音声検索"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を検索"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を音声検索"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"再生"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"一時停止"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"早送り"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"早送り%1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"巻き戻し"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"巻き戻し%1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"次の曲にスキップ"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"前の曲にスキップ"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"その他の操作"</string>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index 94350c7..70aeada 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -22,10 +22,18 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"თქვით საძიებო ფრაზა"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის ძიება"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"თქვით <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის საძიებლად"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"დაკვრა"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"პაუზა"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"წინ გადახვევა"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
+    <skip />
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"უკან გადახვევა"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for lb_playback_controls_rewind_multiplier (1640629531440849942) -->
+    <skip />
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"შემდეგის გამოტოვება"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"წინას გამოტოვება"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"დამატებითი ქმედებები"</string>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index d683255..9ed6ce2 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Іздеу үшін сөйлеу"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу үшін сөйлеңіз"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Ойнату"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Кідірту"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Алға айналдыру"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX алға айналдыру"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Кері айналдыру"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX кері айналдыру"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Келесіге өту"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Алдыңғыға өту"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Қосымша әрекеттер"</string>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index baa2ca8..7874af2 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"និយាយ​​ដើម្បី​ស្វែងរក"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"និយាយ​ដើម្បី​ស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ចាក់"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"ផ្អាក"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"បញ្ជូន​បន្ត​រហ័ស"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ខាទៅមុខ %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ខា​ថយក្រោយ"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ខាថយក្រោយ %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"រំលង​បន្ទាប់"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"រំលង​មុន"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"សកម្មភាព​ច្រើន​ទៀត"</string>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 34688ef..196b154 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ಹುಡುಕಲು ಮಾತನಾಡಿ"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಹುಡುಕಿ"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಮಾತನಾಡಿ ಹುಡುಕಾಟ ನಡೆಸಿ"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ಪ್ಲೇ ಮಾಡು"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"ವಿರಾಮಗೊಳಿಸು"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ಫಾಸ್ಟ್ ಫಾರ್ವರ್ಡ್"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ಫಾಸ್ಟ್ ಫಾರ್ವರ್ಡ್ %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ರೀವೈಂಡ್"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"ರಿವೈಂಡ್ %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ಮುಂದೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ಹಿಂದೆ ಸ್ಕಿಪ್ ಮಾಡಿ"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ಹೆಚ್ಚು ಕ್ರಿಯೆಗಳು"</string>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index f01f79e..c244dbf 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"음성 검색"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 검색"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 음성 검색"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d배속"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d배속"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"재생"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"일시중지"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"빨리 감기"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$d배속 빨리 감기"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"되감기"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$d배속 되감기"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"다음으로 건너뛰기"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"이전으로 건너뛰기"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"추가 작업"</string>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index dd84a54..4ddb284 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Издөө үчүн сүйлөңүз"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө үчүн сүйлөңүз"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Ойнотуу"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Тындыруу"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Алдыга түрүү"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Алдыга түрүү %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Артка түрүү"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Артка түрүү %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Кийинкини өткөрүп жиберүү"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Мурункуну өткөрүп жиберүү"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Дагы көнүгүүлөр"</string>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index e1edf0d..35f519b 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ເວົ້າ​ເພື່ອ​ຊອກ​ຫາ"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ຊອກ​ຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"ເວົ້າ​ເພື່ອ​ຊອກ​ຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ຫຼິ້ນ"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"ຢຸດຊົ່ວຄາວ"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ເລື່ອນ​ໄປ​ໜ້າ"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ໄປ​ໜ້າແບບໄວ %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"​ຣີ​ວາຍກັບ"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"​ກັບ​ຄືນ %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"​ຂ້າມ​ໄປ​ຕໍ່"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"​ຂ້າມ​ໄປ​ກ່ອນ​ໜ້າ"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"ຄຳສັ່ງ​ເພີ່ມເຕີມ"</string>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index 5efd468..6ca2bab 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Pasakykite, kad ieškotumėte"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ieškoti „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kalbėkite, kad ieškotumėte „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d k."</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d k."</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Leisti"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pristabdyti"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Sukti pirmyn"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Sukti pirmyn %1$d k. greičiau"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Sukti atgal"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Sukti atgal %1$d k. greičiau"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Praleisti kitą"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Praleisti ankstesnį"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Daugiau veiksmų"</string>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index 88a0633..7d3bc2b 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Runāt, lai meklētu"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Meklējiet <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Runājiet, lai meklētu: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Atskaņot"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauzēt"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Pārtīt uz priekšu"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Pārtīt uz priekšu %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Attīt atpakaļ"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Attīt atpakaļ %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Izlaist nākamo"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Izlaist iepriekšējo"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Citas darbības"</string>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index a65caa1..75666e0 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Зборувајте за да пребарувате"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Пребарувај <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Кажете за да се пребарува <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Пушти"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Пауза"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Брзо премотај напред"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Премотај напред %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Премотај назад"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Премотај назад %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Прескокни на следна"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Прескокни на претходна"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Повеќе дејства"</string>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index 1d2b8ac..b900f09 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ശബ്‌ദം ഉപയോഗിച്ച് തിരയുക"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുക"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുന്നതിന് സംസാരിക്കുക"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"പ്ലേ ചെയ്യുക"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"താൽക്കാലികമായി നിർത്തുക"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ഫാസ്റ്റ് ഫോർവേഡ് ചെയ്യുക"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX വേഗത്തിൽ ഫോർവേഡുചെയ്യുക"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"റിവൈൻഡുചെയ്യുക"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX റിവൈൻഡുചെയ്യുക"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"അടുത്തതിലേക്ക് പോകുക"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"മുമ്പത്തേതിലേക്ക് പോകുക"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"കൂടുതൽ പ്രവർത്തനങ്ങൾ"</string>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index 6f7fccf..e4a8fcd 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ярьж хайх"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> Хайх"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> хайхын тулд ярина уу"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Тоглуулах"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Түр зогсоох"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Хурдан урагшлуулах"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Түргэн Урагш Гүйлгэх %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Буцааж хураах"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Хойш Гүйлгэх %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Дараагийнхийг алгасах"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Өмнөхийг алгасах"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Өөр үйлдлүүд"</string>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index 84207c3..11748ec 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"शोधण्यासाठी बोला"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधा"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधण्यासाठी बोला"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"प्ले करा"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"विराम द्या"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फास्ट फॉरवर्ड करा"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"फास्ट फॉरवर्ड %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"रिवाईँड करा"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"रीवाईंड %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"पुढील वगळा"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"मागील वगळा"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक क्रिया"</string>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index 25a9b47..c073e43 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tutur untuk membuat carian"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Sebut untuk mencari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Main"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Jeda"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Mara Laju"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Lajukan %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Gulung semula"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Gulung semula %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Langkau Seterusnya"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Langkau Sebelumnya"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Lagi Tindakan"</string>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index ca9fc7a..2efaf7f 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ရှာဖွေရန် ပြောပါ"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ကို ရှာရန်"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ကို ရှာရန် ပြောပါ"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ဖွင့်ရန်"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"ခဏရပ်ရန်"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"ရှေ့သို့ သွားရန်"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"ရှေ့သို့ ရစ်ရန် %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ပြန်ရစ်ရန်"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"နောက်သို့ ရစ်ရန် %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"န​ောက်တစ်ပုဒ်သို့ ကျော်ရန်"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ယခင်တစ်ပုဒ်သို့ သွားရန်"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"နောက်ထပ် လုပ်ဆောင်ချက်များ"</string>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index bef4244..f5ab2e1 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Snakk for å søke"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søk i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Snakk for å søke i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Spill av"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sett på pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Fremoverspoling"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Fremoverspoling %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Tilbakespoling"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tilbakespoling %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Hopp til neste"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Hopp til forrige"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Flere handlinger"</string>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 55d4661..c399985 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -22,10 +22,16 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजी गर्न बोल्नुहोस्"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोज्नुहोस्"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजी गर्न बोल्नुहोस्"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"प्ले गर्नुहोस्"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"रोक्नुहोस्"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"फास्ट फर्वार्ड"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
+    <skip />
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"दोहोर्याउनुहोस्"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"पुन: वाइन्ड गर्नुहोस् %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"अर्को छोड्नुहोस्"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"अघिल्लो छोड्नुहोस्"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"थप कार्यहरू"</string>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 057638f..fe73141 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Spreek om te zoeken"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> zoeken"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Spreek om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te zoeken"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Afspelen"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Onderbreken"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Vooruitspoelen"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Vooruitspoelen %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Terugspoelen"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Terugspoelen %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Naar volgende"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Naar vorige"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Meer acties"</string>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index cb7f377..f6280a3 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Powiedz, aby wyszukać"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Szukaj <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, by wyszukać w aplikacji <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Odtwórz"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Wstrzymaj"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Przewiń do przodu"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Przewiń do przodu %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Przewiń do tyłu"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Przewiń do tyłu %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Pomiń następny"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Pomiń poprzedni"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Więcej czynności"</string>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index 0c3fc3a..f3bf4aa 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduzir"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Interromper"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avançar"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avançar %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Recuar"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Recuar %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Avançar para o seguinte"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Avançar para o anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mais ações"</string>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 3116f83..13d01a5 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduzir"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausar"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Avançar"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Avançar %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Retroceder"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Retroceder %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Pular próxima"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Pular anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mais ações"</string>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index 2186425..cb6aa4a 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Rostiți pentru a căuta"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Căutați <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vorbiți pentru a căuta în <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Redă"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Întrerupe"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Derulează rapid înainte"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Derulați rapid înainte cu %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Derulează"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Derulați înapoi cu %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ignoră articolul următor"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ignoră articolul anterior"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Mai multe acţiuni"</string>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index a1b2cfb..fb03f9d 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Произнесите запрос"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Поиск здесь: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Произнесите запрос, чтобы найти <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Воспроизвести."</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Приостановить."</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Перемотка вперед."</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Перемотка вперед %1$dX."</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Перемотать назад."</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Перемотка назад %1$dX."</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Перейти к следующему элементу."</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Перейти к предыдущему элементу."</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Другие действия."</string>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index 65c270e..e5c0cf4 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"සෙවීමට කථා කරන්න"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න කථා කරන්න"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ධාවනය කරන්න"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"විරාමය"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"වේගයෙන් ඉදිරියට යන"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX වේගයෙන් ඉදිරියට යවන්න"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"නැවත ඔතන්න"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX ආපස්සට යවන්න"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ඊළඟ එක මග අරින්න"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"කළින් එක මග අරින්න"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"තව ක්‍රියාකාරකම්"</string>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index 0bfe3c5..74a9044 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Hovorením spustíte vyhľadávanie"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Vyhľadať výraz <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Hovorte na vyhľadávanie v kontexte <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Prehrať"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pozastaviť"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Pretočiť dopredu"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Pretočiť dopredu %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Pretočiť späť"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Pretočiť späť %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Prejsť na ďalšiu položku"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Prejsť na predchádzajúcu položku"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Viac akcií"</string>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index 9bf2760..1af639b 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite, če želite iskati"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Iskanje: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Govorite, če želite iskati: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d-kratno"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d-kratno"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Predvajaj"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Zaustavi"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Previj naprej"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Hitro previjanje naprej – %1$d-kratno"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Previj nazaj"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Previjanje nazaj – %1$d-kratno"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči naslednje"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prejšnje"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Več dejanj"</string>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index 8f37cab..bb5c32d 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорите да бисте претраживали"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Претражите <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Изговорите да бисте претражили <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Пусти"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Паузирај"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Премотај унапред"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Премотај унапред %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Премотај уназад"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Премотај уназад %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Прескочи следећу"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Прескочи претходну"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Још радњи"</string>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index 6fc1224..1a8e757 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Säg det du söker efter"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sök i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tala för att söka i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Spela upp"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pausa"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Snabbspola framåt"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Spola framåt %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Spola tillbaka"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Spola tillbaka %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Hoppa till nästa"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Hoppa till föregående"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Fler åtgärder"</string>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index b6d44cf..17c7480 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tamka ili utafute"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tafuta <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tamka ili utafute <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Google Play"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Sitisha"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Peleka mbele Haraka"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Peleka Mbele %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Rudisha nyuma"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Peleka nyuma %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ruka Inayofuata"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ruka Iliyotangulia"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Vitendo zaidi"</string>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index e60092c..9472522 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"தேட, பேசவும்"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேடுக"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேட, பேசவும்"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"இயக்கு"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"இடைநிறுத்து"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"வேகமாக முன் நகர்த்து"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX வேகத்தில் முன்செல்"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"வேகமாக பின் நகர்த்து"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX வேகத்தில் பின்செல்"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"அடுத்ததைத் தவிர்"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"முந்தையதைத் தவிர்"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"மேலும் செயல்கள்"</string>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index 762d13a..f71e8cb 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"శోధించడానికి చదివి వినిపించండి"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించండి"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించడానికి చదివి వినిపించండి"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"ప్లే చేయి"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"పాజ్ చేయి"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"వేగంగా ఫార్వార్డ్ చేయి"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX ఫాస్ట్ ఫార్వార్డ్ చేయి"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"రివైండ్ చేయి"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX రివైండ్ చేయి"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"తదుపరి దానికి దాటవేయి"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"మునుపటి దానికి దాటవేయి"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"మరిన్ని చర్యలు"</string>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index 5ba438d0..581bac0 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"พูดเพื่อค้นหา"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"พูดเพื่อค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"เล่น"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"หยุดชั่วคราว"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"กรอไปข้างหน้า"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"กรอไปข้างหน้า %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"กรอกลับ"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"กรอกลับ %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"ข้ามไปรายการถัดไป"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"ข้ามไปรายการก่อนหน้า"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"การทำงานเพิ่มเติม"</string>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index 1198c66..c4e15ec 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Magsalita upang maghanap"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Magsalita upang hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"I-play"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"I-pause"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"I-fast Forward"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"I-fast Forward %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"I-rewind"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"I-rewind %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Laktawan ang Susunod"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Laktawan ang Nakaraan"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Higit Pang Mga Pagkilos"</string>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index 77c846b..4671058 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Arama yapmak için konuşun"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ara: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Aramak için konuşun: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Oynat"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Duraklat"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"İleri Sar"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX İleri Sar"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Geri Sar"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX Geri Sar"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Sonrakine Atla"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Öncekine Atla"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Diğer İşlemler"</string>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index 127c005..79b2782 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Продиктуйте пошуковий запит"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Шукати: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Продиктуйте запит для пошуку: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Відтворити"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Призупинити"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Перемотати вперед"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Перемотати вперед %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Перемотати назад"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Перемотати назад %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Пропустити наступний елемент"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Пропустити попередній елемент"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Інші дії"</string>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index 7be98fc..b670251 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"تلاش کرنے کیلئے بولیں"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کریں"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کرنے کیلئے بولیں"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"چلائیں"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"موقوف کریں"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"تیزی سے فارورڈ کریں"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"‏تیزی سے فارورڈ کریں ‎%1$dX‎"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"ریوائینڈ کریں"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"‏ریوائنڈ کریں ‎%1$dX‎"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"اگلے پر جائیں"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"پچھلے پر جائیں"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"مزید کارروائیاں"</string>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index cf16b8d..235d88f 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Qidirish uchun gapiring"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Qidirish: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Qidirish uchun ayting: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Ijro qilish"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"To‘xtatib turish"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Oldinga o‘tkazish"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$dX tezlikda oldinga o‘tkazish"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Orqaga qaytarish"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$dX tezlikda orqaga qaytarish"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Keyingisiga o‘tish"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Avvalgisiga qaytish"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Boshqa amallar"</string>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index b265491..201d137 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Nói để tìm kiếm"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Nói để tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Phát"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Tạm dừng"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Tua nhanh"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Tua đi %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Tua lại"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Tua lại %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Chuyển đến mục tiếp theo"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Chuyển về mục trước"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Tác vụ khác"</string>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index 5f81c7e..276e7bb 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"说话即可开始搜索"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜索<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"说话即可在<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>中搜索"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d 倍速"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d 倍速"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"暂停"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"快进"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"%1$d 倍速快进"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"快退"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"%1$d 倍速快退"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"跳至下一个"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一个"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多操作"</string>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 264cc64..5e87989 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"暫停"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"向前快轉"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"快轉 %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"向後倒轉"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"倒帶 %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"移至下一個媒體項目"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"移至上一個媒體項目"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多動作"</string>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index c1d7bec..67efc40 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"播放"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"暫停"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"向前快轉"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"快轉 %1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"倒轉"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"倒轉 %1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"跳至下一個項目"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一個項目"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"更多動作"</string>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index e4b7a8e..f17455d 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -22,10 +22,14 @@
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Khuluma ukuze useshe"</string>
     <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sesha i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
     <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Khuluma ukuze useshe i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+    <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
     <string name="lb_playback_controls_play" msgid="731953341987346903">"Dlala"</string>
     <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Misa isikhashana"</string>
     <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Iya phambili ngokushesha"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Mikisa phambili ngokushesha i-%1$dX"</string>
     <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Buyisela emuva"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Mikisa emuva i-%1$dX"</string>
     <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Yeqa okulandelayo"</string>
     <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Yeqa kwangaphambilini"</string>
     <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Izenzo eziningi"</string>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 47211ea..7038fff 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -148,12 +148,49 @@
         <attr name="closed_captioning" format="reference"/>
     </declare-styleable>
 
+    <declare-styleable name="lbSlide">
+        <!-- A duplication of Slide attribute slideEdge for KitKat -->
+        <attr name="lb_slideEdge">
+            <!-- Slide to and from the left edge of the Scene. -->
+            <enum name="left" value="0x03" />
+            <!-- Slide to and from the top edge of the Scene. -->
+            <enum name="top" value="0x30" />
+            <!-- Slide to and from the right edge of the Scene. -->
+            <enum name="right" value="0x05" />
+            <!-- Slide to and from the bottom edge of the Scene. -->
+            <enum name="bottom" value="0x50" />
+            <!-- Slide to and from the x-axis position at the start of the Scene root. -->
+            <enum name="start" value="0x00800003"/>
+            <!-- Slide to and from the x-axis position at the end of the Scene root. -->
+            <enum name="end" value="0x00800005"/>
+        </attr>
+        <attr name="android:duration" />
+        <attr name="android:startDelay" />
+        <attr name="android:interpolator" />
+    </declare-styleable>
+
+    <declare-styleable name="lbResizingTextView">
+        <!-- Conditions used to trigger text resizing -->
+        <attr name="resizeTrigger">
+            <!-- Resize text whenever it lays out into the maximum number of lines -->
+            <flag name="maxLines" value="0x01" />
+        </attr>
+        <!-- Text size for resized text -->
+        <attr name="resizedTextSize" format="dimension" />
+        <!-- Whether to maintain the same line spacing when text is resized, default is false -->
+        <attr name="maintainLineSpacing" format="boolean" />
+        <!-- Adjustment to top padding for resized text -->
+        <attr name="resizedPaddingAdjustmentTop" format="dimension" />
+        <!-- Adjustment to bottom padding for resized text -->
+        <attr name="resizedPaddingAdjustmentBottom" format="dimension" />
+    </declare-styleable>
+
     <declare-styleable name="LeanbackTheme">
 
-        <!-- left padding of BrowseFragment, RowsFragment, DetailsFragment -->
-        <attr name="browsePaddingLeft" format="dimension" />
-        <!-- right padding of BrowseFragment, RowsFragment, DetailsFragment -->
-        <attr name="browsePaddingRight" format="dimension" />
+        <!-- start padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingStart" format="dimension" />
+        <!-- end padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingEnd" format="dimension" />
         <!-- top padding of BrowseFragment -->
         <attr name="browsePaddingTop" format="dimension" />
         <!-- bottom padding of BrowseFragment -->
@@ -204,6 +241,7 @@
 
         <!-- for playback controls -->
         <attr name="playbackControlsButtonStyle" format="reference" />
+        <attr name="playbackControlButtonLabelStyle" format="reference" />
         <attr name="playbackControlsTimeStyle" format="reference" />
 
         <!-- style for a vertical grid of items -->
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index 46a5fde..ba65d2f 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -32,12 +32,11 @@
 
     <color name="lb_error_background_color_opaque">#262626</color>
     <color name="lb_error_background_color_translucent">#E6000000</color>
-    <color name="lb_error_message_color_on_opaque">#80EEEEEE</color>
-    <color name="lb_error_message_color_on_translucent">#80555555</color>
+    <color name="lb_error_message">#80EEEEEE</color>
 
     <color name="lb_action_text_color">#EEEEEE</color>
 
-    <color name="lb_search_bar_text">#FFEEEEEE</color>
+    <color name="lb_search_bar_text">#80EEEEEE</color>
     <color name="lb_search_bar_text_speech_mode">#FF444444</color>
     <color name="lb_search_bar_hint">#FF888888</color>
     <color name="lb_search_bar_hint_speech_mode">#66222222</color>
@@ -50,12 +49,13 @@
     <color name="lb_basic_card_bg_color">#FF3B3B3B</color>
     <color name="lb_basic_card_info_bg_color">#FF3B3B3B</color>
     <color name="lb_basic_card_title_text_color">#FFEEEEEE</color>
-    <color name="lb_basic_card_content_text_color">#FF888888</color>
+    <color name="lb_basic_card_content_text_color">#99EEEEEE</color>
 
     <color name="lb_default_brand_color">#FF455A64</color>
     <color name="lb_default_search_color">#FFFFAA3F</color>
 
     <color name="lb_control_button_color">#66EEEEEE</color>
+    <color name="lb_control_button_text">#EEEEEE</color>
     <color name="lb_playback_progress_color_no_theme">#ff40c4ff</color>
     <color name="lb_playback_icon_highlight_no_theme">#ff40c4ff</color>
     <color name="lb_playback_secondary_progress_color">#33FFFFFF</color>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 5e0b4ee..8c4c198 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -17,9 +17,9 @@
 <resources>
     <dimen name="lb_list_row_height">224dp</dimen>
 
-    <dimen name="lb_browse_padding_left">56dp</dimen>
+    <dimen name="lb_browse_padding_start">56dp</dimen>
     <dimen name="lb_browse_padding_top">27dp</dimen>
-    <dimen name="lb_browse_padding_right">56dp</dimen>
+    <dimen name="lb_browse_padding_end">56dp</dimen>
     <dimen name="lb_browse_padding_bottom">48dp</dimen>
     <dimen name="lb_browse_rows_margin_start">238dp</dimen>
     <dimen name="lb_browse_rows_margin_top">167dp</dimen>
@@ -27,9 +27,8 @@
     <dimen name="lb_vertical_grid_padding_bottom">87dp</dimen>
 
     <dimen name="lb_browse_title_height">60dp</dimen>
-    <dimen name="lb_browse_title_icon_height">52dp</dimen>
-    <dimen name="lb_browse_title_icon_width">52dp</dimen>
-    <dimen name="lb_browse_title_icon_margin_right">52dp</dimen>
+    <dimen name="lb_browse_title_icon_max_width">584dp</dimen>
+    <dimen name="lb_browse_title_icon_height">60dp</dimen>
     <dimen name="lb_browse_title_text_size">44sp</dimen>
     <dimen name="lb_browse_title_text_width">584dp</dimen>
 
@@ -44,7 +43,7 @@
     <dimen name="lb_browse_header_text_size">20sp</dimen>
     <dimen name="lb_browse_header_height">24dp</dimen>
     <dimen name="lb_browse_header_fading_length">12dp</dimen>
-    <dimen name="lb_browse_header_padding_right">8dp</dimen>
+    <dimen name="lb_browse_header_padding_end">8dp</dimen>
 
     <item name="lb_browse_header_select_duration" format="integer" type="dimen">150</item>
     <item name="lb_browse_header_unselect_alpha" type="fraction">50%</item>
@@ -59,31 +58,35 @@
     <dimen name="lb_browse_expanded_selected_row_top_padding">16dp</dimen>
     <dimen name="lb_browse_expanded_row_no_hovercard_bottom_padding">28dp</dimen>
 
+    <item name="lb_focus_zoom_factor_xsmall" type="fraction">106%</item>
     <item name="lb_focus_zoom_factor_small" type="fraction">110%</item>
     <item name="lb_focus_zoom_factor_medium" type="fraction">114%</item>
     <item name="lb_focus_zoom_factor_large" type="fraction">118%</item>
 
     <dimen name="lb_details_overview_height_large">274dp</dimen>
     <dimen name="lb_details_overview_height_small">159dp</dimen>
-    <dimen name="lb_details_overview_margin_left">132dp</dimen>
-    <dimen name="lb_details_overview_margin_right">132dp</dimen>
+    <dimen name="lb_details_overview_margin_start">132dp</dimen>
+    <dimen name="lb_details_overview_margin_end">132dp</dimen>
     <dimen name="lb_details_overview_margin_bottom">40dp</dimen>
 
     <dimen name="lb_details_overview_description_margin_top">24dp</dimen>
-    <dimen name="lb_details_overview_description_margin_left">24dp</dimen>
-    <dimen name="lb_details_overview_description_margin_right">24dp</dimen>
+    <dimen name="lb_details_overview_description_margin_start">24dp</dimen>
+    <dimen name="lb_details_overview_description_margin_end">24dp</dimen>
     <dimen name="lb_details_overview_description_margin_bottom">12dp</dimen>
     <dimen name="lb_details_overview_image_margin_horizontal">24dp</dimen>
     <dimen name="lb_details_overview_image_margin_vertical">24dp</dimen>
     <dimen name="lb_details_overview_action_items_margin">16dp</dimen>
     <item name="lb_details_overview_action_select_duration" format="integer" type="dimen">150</item>
-    <dimen name="lb_details_overview_actions_padding_left">294dp</dimen>
-    <dimen name="lb_details_overview_actions_padding_right">132dp</dimen>
+    <dimen name="lb_details_overview_actions_padding_start">294dp</dimen>
+    <dimen name="lb_details_overview_actions_padding_end">132dp</dimen>
     <dimen name="lb_details_overview_actions_height">56dp</dimen>
     <dimen name="lb_details_overview_actions_fade_size">16dp</dimen>
     <dimen name="lb_details_rows_align_top">167dp</dimen>
 
     <dimen name="lb_details_description_title_text_size">34sp</dimen>
+    <dimen name="lb_details_description_title_resized_text_size">28sp</dimen>
+    <dimen name="lb_details_description_title_padding_adjust_top">-1dp</dimen>
+    <dimen name="lb_details_description_title_padding_adjust_bottom">2dp</dimen>
     <dimen name="lb_details_description_subtitle_text_size">16sp</dimen>
     <dimen name="lb_details_description_body_text_size">14sp</dimen>
     <dimen name="lb_details_description_title_line_spacing">40dp</dimen>
@@ -100,8 +103,8 @@
     <dimen name="lb_action_1_line_height">36dp</dimen>
     <dimen name="lb_action_2_lines_height">56dp</dimen>
     <dimen name="lb_action_padding_horizontal">24dp</dimen>
-    <dimen name="lb_action_with_icon_padding_left">14dp</dimen>
-    <dimen name="lb_action_with_icon_padding_right">20dp</dimen>
+    <dimen name="lb_action_with_icon_padding_start">14dp</dimen>
+    <dimen name="lb_action_with_icon_padding_end">20dp</dimen>
     <dimen name="lb_action_icon_margin">12dp</dimen>
     <dimen name="lb_action_text_size">16sp</dimen>
     <dimen name="lb_action_button_corner_radius">2dp</dimen>
@@ -111,8 +114,8 @@
     <dimen name="lb_playback_major_fade_translate_y">200dp</dimen>
     <dimen name="lb_playback_minor_fade_translate_y">16dp</dimen>
     <dimen name="lb_playback_controls_card_height">176dp</dimen>
-    <dimen name="lb_playback_controls_margin_left">132dp</dimen>
-    <dimen name="lb_playback_controls_margin_right">132dp</dimen>
+    <dimen name="lb_playback_controls_margin_start">132dp</dimen>
+    <dimen name="lb_playback_controls_margin_end">132dp</dimen>
     <dimen name="lb_playback_controls_margin_bottom">20dp</dimen>
     <dimen name="lb_playback_description_margin_top">24dp</dimen>
     <dimen name="lb_playback_description_margin_start">24dp</dimen>
@@ -131,6 +134,7 @@
     <dimen name="lb_control_button_secondary_height">48dp</dimen>
     <dimen name="lb_control_icon_width">32dp</dimen>
     <dimen name="lb_control_icon_height">32dp</dimen>
+    <dimen name="lb_control_button_text_size">22sp</dimen>
 
     <dimen name="lb_error_image_max_height">120dp</dimen>
     <integer name="lb_error_message_max_lines">1</integer>
@@ -144,27 +148,27 @@
 
     <!-- Search bar -->
     <dimen name="lb_search_bar_height">60dp</dimen>
-    <dimen name="lb_search_bar_padding_left">56dp</dimen>
+    <dimen name="lb_search_bar_padding_start">56dp</dimen>
     <dimen name="lb_search_bar_padding_top">27dp</dimen>
 
-    <dimen name="lb_search_bar_text_size">22sp</dimen>
+    <dimen name="lb_search_bar_text_size">18sp</dimen>
     <dimen name="lb_search_bar_unfocused_text_size">18sp</dimen>
     <dimen name="lb_search_bar_items_layout_margin_top">27dp</dimen>
     <dimen name="lb_search_bar_items_width">600dp</dimen>
     <dimen name="lb_search_bar_items_height">56dp</dimen>
-    <dimen name="lb_search_bar_items_margin_left">70dp</dimen>
+    <dimen name="lb_search_bar_items_margin_start">70dp</dimen>
     <dimen name="lb_search_bar_inner_margin_top">2dp</dimen>
     <dimen name="lb_search_bar_inner_margin_bottom">2dp</dimen>
     <dimen name="lb_search_bar_icon_height">32dp</dimen>
     <dimen name="lb_search_bar_icon_width">32dp</dimen>
-    <dimen name="lb_search_bar_icon_margin_left">16dp</dimen>
-    <dimen name="lb_search_bar_edit_text_margin_left">24dp</dimen>
-    <dimen name="lb_search_bar_hint_margin_left">52dp</dimen>
+    <dimen name="lb_search_bar_icon_margin_start">16dp</dimen>
+    <dimen name="lb_search_bar_edit_text_margin_start">24dp</dimen>
+    <dimen name="lb_search_bar_hint_margin_start">52dp</dimen>
 
 
     <!-- Search Fragment -->
     <dimen name="lb_search_browse_rows_align_top">120dp</dimen>
-    <dimen name="lb_search_browse_row_padding_left">56dp</dimen>
+    <dimen name="lb_search_browse_row_padding_start">56dp</dimen>
 
     <dimen name="lb_search_orb_size">52dp</dimen>
     <item name="lb_search_orb_focused_zoom" type="fraction">120%</item>
@@ -173,12 +177,12 @@
 
     <dimen name="lb_search_orb_margin_top">4dp</dimen>
     <dimen name="lb_search_orb_margin_bottom">4dp</dimen>
-    <dimen name="lb_search_orb_margin_left">4dp</dimen>
-    <dimen name="lb_search_orb_margin_right">4dp</dimen>
+    <dimen name="lb_search_orb_margin_start">4dp</dimen>
+    <dimen name="lb_search_orb_margin_end">4dp</dimen>
 
     <dimen name="lb_search_bar_speech_orb_size">52dp</dimen>
     <item name="lb_search_bar_speech_orb_max_level_zoom" type="fraction">144%</item>
-    <dimen name="lb_search_bar_speech_orb_margin_left">56dp</dimen>
+    <dimen name="lb_search_bar_speech_orb_margin_start">56dp</dimen>
 
     <!-- BasicCardView -->
     <dimen name="lb_basic_card_main_width">140dp</dimen>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 289927f..2cd0ff11 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -25,6 +25,10 @@
     <string name="lb_search_bar_hint_with_title">Search <xliff:g id="search context">%1$s</xliff:g></string>
     <!-- Hint showing in the empty search bar using a provided context (usually the application name) while in voice input mode [CHAR LIMIT=40] -->
     <string name="lb_search_bar_hint_with_title_speech">Speak to search <xliff:g id="search context">%1$s</xliff:g></string>
+    <!-- Onscreen label for the control button to fast forward media playback at a given speed multiplier -->
+    <string name="lb_control_display_fast_forward_multiplier">%1$dX</string>
+    <!-- Onscreen label for the control button to rewind media playback at a given speed multiplier -->
+    <string name="lb_control_display_rewind_multiplier">%1$dX</string>
 
     <!-- Talkback label for the control button to start media playback -->
     <string name="lb_playback_controls_play">Play</string>
@@ -32,8 +36,12 @@
     <string name="lb_playback_controls_pause">Pause</string>
     <!-- Talkback label for the control button to fast forward media playback -->
     <string name="lb_playback_controls_fast_forward">Fast Forward</string>
+    <!-- Talkback label for the control button to fast forward media playback at a given speed multiplier -->
+    <string name="lb_playback_controls_fast_forward_multiplier">Fast Forward %1$dX</string>
     <!-- Talkback label for the control button to start rewind playback -->
     <string name="lb_playback_controls_rewind">Rewind</string>
+    <!-- Talkback label for the control button to rewind media playback at a given speed multiplier -->
+    <string name="lb_playback_controls_rewind_multiplier">Rewind %1$dX</string>
     <!-- Talkback label for the control button to skip to the next media item -->
     <string name="lb_playback_controls_skip_next">Skip Next</string>
     <!-- Talkback label for the control button to skip to the previous media item -->
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index f1e883e..32f2f77 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -71,9 +71,15 @@
         <item name="android:fontFamily">sans-serif</item>
     </style>
 
+    <style name="TextAppearance.Leanback.PlaybackControlLabel">
+        <item name="android:textSize">@dimen/lb_control_button_text_size</item>
+        <item name="android:textColor">@color/lb_control_button_text</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
     <style name="TextAppearance.Leanback.ErrorMessage">
         <item name="android:textSize">@dimen/lb_error_message_text_size</item>
-        <item name="android:textColor">@color/lb_error_message_color_on_opaque</item>
+        <item name="android:textColor">@color/lb_error_message</item>
         <item name="android:fontFamily">sans-serif</item>
     </style>
 
@@ -98,13 +104,12 @@
 
     <style name="Widget.Leanback.Title.Text">
         <item name="android:singleLine">true</item>
-        <item name="android:gravity">right</item>
+        <item name="android:gravity">end</item>
         <item name="android:ellipsize">end</item>
         <item name="android:textAppearance">@style/TextAppearance.Leanback.Title</item>
     </style>
 
     <style name="Widget.Leanback.Title.Icon">
-        <item name="android:scaleType">fitEnd</item>
     </style>
 
     <!-- HeadersFragment -->
@@ -121,7 +126,7 @@
     <style name="Widget.Leanback.GridItems" />
 
     <style name="Widget.Leanback.Headers.VerticalGridView" >
-        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+        <item name="android:paddingStart">?attr/browsePaddingStart</item>
         <item name="android:clipToPadding">false</item>
         <item name="focusOutFront">true</item>
         <item name="focusOutEnd">true</item>
@@ -132,6 +137,7 @@
 
     <style name="Widget.Leanback.Header" >
         <item name="android:minHeight">@dimen/lb_browse_header_height</item>
+        <item name="android:minWidth">1dp</item>
         <item name="android:textAppearance">@style/TextAppearance.Leanback.Header</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">none</item>
@@ -150,8 +156,8 @@
         <item name="android:clipToPadding">false</item>
         <item name="android:focusable">true</item>
         <item name="android:focusableInTouchMode">true</item>
-        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
-        <item name="android:paddingRight">?attr/browsePaddingRight</item>
+        <item name="android:paddingStart">?attr/browsePaddingStart</item>
+        <item name="android:paddingEnd">?attr/browsePaddingEnd</item>
         <item name="android:paddingBottom">@dimen/lb_browse_item_vertical_margin</item>
         <item name="android:paddingTop">@dimen/lb_browse_item_vertical_margin</item>
         <item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
@@ -164,8 +170,8 @@
         <item name="android:clipToPadding">false</item>
         <item name="android:focusable">true</item>
         <item name="android:focusableInTouchMode">true</item>
-        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
-        <item name="android:paddingRight">?attr/browsePaddingRight</item>
+        <item name="android:paddingStart">?attr/browsePaddingStart</item>
+        <item name="android:paddingEnd">?attr/browsePaddingEnd</item>
         <item name="android:paddingBottom">@dimen/lb_vertical_grid_padding_bottom</item>
         <item name="android:paddingTop">?attr/browseRowsMarginTop</item>
         <item name="android:gravity">center_horizontal</item>
@@ -206,6 +212,10 @@
         <item name="android:maxLines">@integer/lb_details_description_title_max_lines</item>
         <item name="android:includeFontPadding">false</item>
         <item name="android:ellipsize">end</item>
+        <item name="resizeTrigger">maxLines</item>
+        <item name="resizedTextSize">@dimen/lb_details_description_title_resized_text_size</item>
+        <item name="resizedPaddingAdjustmentTop">@dimen/lb_details_description_title_padding_adjust_top</item>
+        <item name="resizedPaddingAdjustmentBottom">@dimen/lb_details_description_title_padding_adjust_bottom</item>
     </style>
 
     <style name="Widget.Leanback.DetailsDescriptionSubtitleStyle">
@@ -230,8 +240,8 @@
         <item name="android:drawablePadding">@dimen/lb_action_icon_margin</item>
         <item name="android:focusable">true</item>
         <item name="android:focusableInTouchMode">true</item>
-        <item name="android:paddingLeft">@dimen/lb_action_padding_horizontal</item>
-        <item name="android:paddingRight">@dimen/lb_action_padding_horizontal</item>
+        <item name="android:paddingStart">@dimen/lb_action_padding_horizontal</item>
+        <item name="android:paddingEnd">@dimen/lb_action_padding_horizontal</item>
     </style>
 
     <style name="Widget.Leanback.PlaybackControlsButtonStyle" >
@@ -239,6 +249,10 @@
         <item name="android:focusableInTouchMode">true</item>
     </style>
 
+    <style name="Widget.Leanback.PlaybackControlLabelStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlLabel</item>
+    </style>
+
     <style name="Widget.Leanback.PlaybackControlsTimeStyle">
         <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlsTime</item>
     </style>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 8b66edf..0503e17 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -31,8 +31,8 @@
         <item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
         <item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
 
-        <item name="browsePaddingLeft">@dimen/lb_browse_padding_left</item>
-        <item name="browsePaddingRight">@dimen/lb_browse_padding_right</item>
+        <item name="browsePaddingStart">@dimen/lb_browse_padding_start</item>
+        <item name="browsePaddingEnd">@dimen/lb_browse_padding_end</item>
         <item name="browsePaddingTop">@dimen/lb_browse_padding_top</item>
         <item name="browsePaddingBottom">@dimen/lb_browse_padding_bottom</item>
         <item name="browseRowsMarginStart">@dimen/lb_browse_rows_margin_start</item>
@@ -61,6 +61,7 @@
         <item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
         <item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
         <item name="playbackControlsButtonStyle">@style/Widget.Leanback.PlaybackControlsButtonStyle</item>
+        <item name="playbackControlButtonLabelStyle">@style/Widget.Leanback.PlaybackControlLabelStyle</item>
         <item name="playbackControlsTimeStyle">@style/Widget.Leanback.PlaybackControlsTimeStyle</item>
         <item name="playbackControlsActionIcons">@style/Widget.Leanback.PlaybackControlsActionIconsStyle</item>
 
@@ -71,14 +72,31 @@
         <item name="defaultSearchBrightColor">@null</item>
         <item name="defaultSearchIcon">@null</item>
 
+        <!-- android:windowSharedElementEnterTransition is kept for backward compatibility for apps still refer
+        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
         <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
-        <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
+        <!-- android:windowSharedElementReturnTransition is kept for backward compatibility for apps still refer
+        to Theme.Leanback, app should use Theme.Leanback.Details instead -->
         <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+        <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
         <item name="android:windowReturnTransition">@transition/lb_return_transition</item>
+        <item name="android:windowTransitionBackgroundFadeDuration">350</item>
 
         <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">
+        <item name="android:windowEnterTransition">@transition/lb_browse_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_browse_return_transition</item>
+    </style>
+
+    <style name="Theme.Leanback.Details" parent="Theme.Leanback">
+        <item name="android:windowEnterTransition">@transition/lb_details_enter_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_details_return_transition</item>
+        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
+        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+    </style>
+
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index 5cf75f3..75fc5f6 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.Handler;
+import android.support.v4.view.animation.FastOutLinearInInterpolator;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -39,7 +40,8 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
+import android.view.animation.AnimationUtils;
+import android.support.v4.app.FragmentActivity;
 import android.support.v4.content.ContextCompat;
 
 /**
@@ -107,6 +109,12 @@
     private Drawable mBackgroundDrawable;
     private int mBackgroundColor;
     private boolean mAttached;
+    private long mLastSetTime;
+
+    private final Interpolator mAccelerateInterpolator;
+    private final Interpolator mDecelerateInterpolator;
+    private final ValueAnimator mAnimator;
+    private final ValueAnimator mDimAnimator;
 
     private static class BitmapDrawable extends Drawable {
 
@@ -178,23 +186,11 @@
     private static class DrawableWrapper {
         protected int mAlpha;
         protected Drawable mDrawable;
-        protected ValueAnimator mAnimator;
-        protected boolean mAnimationPending;
-
-        private final Interpolator mInterpolator = new LinearInterpolator();
-        private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener =
-                new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                setAlpha((Integer) animation.getAnimatedValue());
-            }
-        };
 
         public DrawableWrapper(Drawable drawable) {
             mDrawable = drawable;
             setAlpha(FULL_ALPHA);
         }
-
         public Drawable getDrawable() {
             return mDrawable;
         }
@@ -208,39 +204,6 @@
         public void setColor(int color) {
             ((ColorDrawable) mDrawable).setColor(color);
         }
-        public void fadeIn(int durationMs, int delayMs) {
-            fade(durationMs, delayMs, FULL_ALPHA);
-        }
-        public void fadeOut(int durationMs) {
-            fade(durationMs, 0, 0);
-        }
-        public void fade(int durationMs, int delayMs, int alpha) {
-            if (mAnimator != null && mAnimator.isStarted()) {
-                mAnimator.cancel();
-            }
-            mAnimator = ValueAnimator.ofInt(getAlpha(), alpha);
-            mAnimator.addUpdateListener(mAnimationUpdateListener);
-            mAnimator.setInterpolator(mInterpolator);
-            mAnimator.setDuration(durationMs);
-            mAnimator.setStartDelay(delayMs);
-            mAnimationPending = true;
-        }
-        public boolean isAnimationPending() {
-            return mAnimationPending;
-        }
-        public boolean isAnimationStarted() {
-            return mAnimator != null && mAnimator.isStarted();
-        }
-        public void startAnimation() {
-            startAnimation(null);
-        }
-        public void startAnimation(Animator.AnimatorListener listener) {
-            if (listener != null) {
-                mAnimator.addListener(listener);
-            }
-            mAnimator.start();
-            mAnimationPending = false;
-        }
     }
 
     private LayerDrawable mLayerDrawable;
@@ -251,8 +214,51 @@
     private DrawableWrapper mDimWrapper;
 
     private Drawable mThemeDrawable;
+    private Drawable mDimDrawable;
     private ChangeBackgroundRunnable mChangeRunnable;
 
+    private final Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            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) {
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int fadeInAlpha = (Integer) animation.getAnimatedValue();
+            if (mImageInWrapper != null) {
+                mImageInWrapper.setAlpha(fadeInAlpha);
+            }
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            if (mDimWrapper != null) {
+                mDimWrapper.setAlpha((Integer) animation.getAnimatedValue());
+            }
+        }
+    };
+
     /**
      * Shared memory continuity service.
      */
@@ -337,6 +343,9 @@
      * for this Activity.
      */
     public static BackgroundManager getInstance(Activity activity) {
+        if (activity instanceof FragmentActivity) {
+            return getSupportInstance((FragmentActivity) activity);
+        }
         BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
                 .findFragmentByTag(FRAGMENT_TAG);
         if (fragment != null) {
@@ -347,16 +356,44 @@
             // manager is null: this is a fragment restored by FragmentManager,
             // fall through to create a BackgroundManager attach to it.
         }
-        return new BackgroundManager(activity);
+        return new BackgroundManager(activity, false);
     }
 
-    private BackgroundManager(Activity activity) {
+    private static BackgroundManager getSupportInstance(FragmentActivity activity) {
+        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
+                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+        if (fragment != null) {
+            BackgroundManager manager = fragment.getBackgroundManager();
+            if (manager != null) {
+                return manager;
+            }
+            // manager is null: this is a fragment restored by FragmentManager,
+            // fall through to create a BackgroundManager attach to it.
+        }
+        return new BackgroundManager(activity, true);
+    }
+
+    private BackgroundManager(Activity activity, boolean isSupportFragmentActivity) {
         mContext = activity;
         mService = BackgroundContinuityService.getInstance();
         mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
         mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
         mHandler = new Handler();
 
+        Interpolator defaultInterpolator = new FastOutLinearInInterpolator();
+        mAccelerateInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.anim.accelerate_interpolator);
+        mDecelerateInterpolator = AnimationUtils.loadInterpolator(mContext,
+                android.R.anim.decelerate_interpolator);
+
+        mAnimator = ValueAnimator.ofInt(0, FULL_ALPHA);
+        mAnimator.addListener(mAnimationListener);
+        mAnimator.addUpdateListener(mAnimationUpdateListener);
+        mAnimator.setInterpolator(defaultInterpolator);
+
+        mDimAnimator = new ValueAnimator();
+        mDimAnimator.addUpdateListener(mDimUpdateListener);
+
         TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
                 android.R.attr.windowBackground });
         mThemeDrawableResourceId = ta.getResourceId(0, -1);
@@ -365,7 +402,11 @@
         }
         ta.recycle();
 
-        createFragment(activity);
+        if (isSupportFragmentActivity) {
+            createSupportFragment((FragmentActivity) activity);
+        } else {
+            createFragment(activity);
+        }
     }
 
     private void createFragment(Activity activity) {
@@ -384,6 +425,23 @@
         fragment.setBackgroundManager(this);
     }
 
+    private void createSupportFragment(FragmentActivity activity) {
+        // Use a fragment to ensure the background manager gets detached properly.
+        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
+                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+        if (fragment == null) {
+            fragment = new BackgroundSupportFragment();
+            activity.getSupportFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG)
+                    .commit();
+        } else {
+            if (fragment.getBackgroundManager() != null) {
+                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
+                    "activity, please use getInstance() instead");
+            }
+        }
+        fragment.setBackgroundManager(this);
+    }
+
     /**
      * Synchronizes state when the owning Activity is resumed.
      */
@@ -428,17 +486,28 @@
 
         mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
 
-        mDimWrapper = new DrawableWrapper(
-                mLayerDrawable.findDrawableByLayerId(R.id.background_dim));
-
         mLayerWrapper = new DrawableWrapper(mLayerDrawable);
 
         mColorWrapper = new DrawableWrapper(
                 mLayerDrawable.findDrawableByLayerId(R.id.background_color));
+
+        updateDimWrapper();
+    }
+
+    private void updateDimWrapper() {
+        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());
+        }
     }
 
     /**
-     * Make the background visible on the given Window.
+     * Make the background visible on the given Window.  The background manager must be attached
+     * when the background is set.
      */
     public void attach(Window window) {
         if (USE_SEPARATE_WINDOW) {
@@ -477,6 +546,13 @@
     }
 
     /**
+     * Returns true if the background manager is currently attached; false otherwise.
+     */
+    public boolean isAttached() {
+        return mAttached;
+    }
+
+    /**
      * Release references to Drawables and put the BackgroundManager into the
      * detached state. Called when the associated Activity is destroyed.
      * @hide
@@ -523,7 +599,7 @@
         mDimWrapper = null;
         mThemeDrawable = null;
         if (mChangeRunnable != null) {
-            mChangeRunnable.cancel();
+            mHandler.removeCallbacks(mChangeRunnable);
             mChangeRunnable = null;
         }
         releaseBackgroundBitmap();
@@ -533,6 +609,28 @@
         mBackgroundDrawable = null;
     }
 
+    /**
+     * Sets the drawable used as a dim layer.
+     */
+    public void setDimLayer(Drawable drawable) {
+        mDimDrawable = drawable;
+        updateDimWrapper();
+    }
+
+    /**
+     * Returns the drawable used as a dim layer.
+     */
+    public Drawable getDimLayer() {
+        return mDimDrawable;
+    }
+
+    /**
+     * Returns the default drawable used as a dim layer.
+     */
+    public Drawable getDefaultDimLayer() {
+        return ContextCompat.getDrawable(mContext, R.color.lb_background_protection);
+    }
+
     private void updateImmediate() {
         lazyInit();
 
@@ -587,23 +685,30 @@
         if (!mAttached) {
             throw new IllegalStateException("Must attach before setting background drawable");
         }
+        long delayMs = getRunnableDelay();
+        mLastSetTime = System.currentTimeMillis();
 
         if (mChangeRunnable != null) {
             if (sameDrawable(drawable, mChangeRunnable.mDrawable)) {
-                if (DEBUG) Log.v(TAG, "setting same drawable");
+                if (DEBUG) Log.v(TAG, "new drawable same as pending");
                 return;
             }
-            mChangeRunnable.cancel();
+            mHandler.removeCallbacks(mChangeRunnable);
         }
         mChangeRunnable = new ChangeBackgroundRunnable(drawable);
 
-        if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) {
+        if (mAnimator.isStarted()) {
             if (DEBUG) Log.v(TAG, "animation in progress");
         } else {
-            mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
+            if (DEBUG) Log.v(TAG, "posting runnable delayMs " + delayMs);
+            mHandler.postDelayed(mChangeRunnable, delayMs);
         }
     }
 
+    private long getRunnableDelay() {
+        return Math.max(0, mLastSetTime + CHANGE_BG_DELAY_MS - System.currentTimeMillis());
+    }
+
     /**
      * Set the given bitmap into the background. When using setBitmap to set the
      * background, the provided bitmap will be scaled and cropped to correctly
@@ -664,13 +769,10 @@
 
         if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);
 
-        int dimAlpha = 0;
+        int dimAlpha = -1;
 
-        if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) {
-            if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting");
-            mImageOutWrapper.startAnimation();
-            mImageOutWrapper = null;
-            dimAlpha = DIM_ALPHA_ON_SOLID;
+        if (mImageOutWrapper != null) {
+            dimAlpha = mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID;
         }
 
         if (mImageInWrapper == null && mBackgroundDrawable != null) {
@@ -679,37 +781,23 @@
             mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
             if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
             mImageInWrapper.setAlpha(0);
-            mImageInWrapper.fadeIn(FADE_DURATION, 0);
-            mImageInWrapper.startAnimation(mImageInListener);
             dimAlpha = FULL_ALPHA;
         }
 
-        if (mDimWrapper != null && dimAlpha != 0) {
+        mAnimator.setDuration(FADE_DURATION);
+        mAnimator.start();
+
+        if (mDimWrapper != null && dimAlpha >= 0) {
             if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
-            mDimWrapper.fade(FADE_DURATION, 0, dimAlpha);
-            mDimWrapper.startAnimation();
+            mDimAnimator.cancel();
+            mDimAnimator.setIntValues(mDimWrapper.getAlpha(), dimAlpha);
+            mDimAnimator.setDuration(FADE_DURATION);
+            mDimAnimator.setInterpolator(
+                    dimAlpha == FULL_ALPHA ? mDecelerateInterpolator : mAccelerateInterpolator);
+            mDimAnimator.start();
         }
     }
 
-    private final Animator.AnimatorListener mImageInListener = new Animator.AnimatorListener() {
-        @Override
-        public void onAnimationStart(Animator animation) {
-        }
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-        }
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (mChangeRunnable != null) {
-                if (DEBUG) Log.v(TAG, "animation ended, found change runnable");
-                mChangeRunnable.run();
-            }
-        }
-        @Override
-        public void onAnimationCancel(Animator animation) {
-        }
-    };
-
     /**
      * Returns the current background color.
      */
@@ -744,44 +832,37 @@
      */
     class ChangeBackgroundRunnable implements Runnable {
         private Drawable mDrawable;
-        private boolean mCancel;
 
         ChangeBackgroundRunnable(Drawable drawable) {
             mDrawable = drawable;
         }
 
-        public void cancel() {
-            mCancel = true;
-        }
-
         @Override
         public void run() {
-            if (!mCancel) {
-                runTask();
-            }
+            runTask();
+            mChangeRunnable = null;
         }
 
         private void runTask() {
             lazyInit();
 
             if (sameDrawable(mDrawable, mBackgroundDrawable)) {
-                if (DEBUG) Log.v(TAG, "same bitmap detected");
+                if (DEBUG) Log.v(TAG, "new drawable same as current");
                 return;
             }
 
             releaseBackgroundBitmap();
 
             if (mImageInWrapper != null) {
+                if (DEBUG) Log.v(TAG, "moving image in to image out");
                 mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
-                mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha());
-                mImageOutWrapper.fadeOut(FADE_DURATION);
+                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());
-                mImageInWrapper.setAlpha(0);
                 mImageInWrapper = null;
             }
 
@@ -789,8 +870,6 @@
             mService.setDrawable(mBackgroundDrawable);
 
             applyBackgroundChanges();
-
-            mChangeRunnable = null;
         }
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
new file mode 100644
index 0000000..3b6530a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
@@ -0,0 +1,56 @@
+/* This file is auto-generated from BackgroundFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+
+/**
+ * Fragment used by the background manager.
+ * @hide
+ */
+public final class BackgroundSupportFragment extends Fragment {
+    private BackgroundManager mBackgroundManager;
+
+    void setBackgroundManager(BackgroundManager backgroundManager) {
+        mBackgroundManager = backgroundManager;
+    }
+
+    BackgroundManager getBackgroundManager() {
+        return mBackgroundManager;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // mBackgroundManager might be null:
+        // if BackgroundSupportFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.onActivityResume();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // mBackgroundManager might be null:
+        // if BackgroundSupportFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.detach();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
new file mode 100644
index 0000000..07e6123
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * @hide
+ */
+class BaseFragment extends Fragment {
+
+    private boolean mEntranceTransitionEnabled = false;
+    private boolean mStartEntranceTransitionPending = false;
+    private Object mEntranceTransition;
+
+    static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        if (mStartEntranceTransitionPending) {
+            mStartEntranceTransitionPending = false;
+            startEntranceTransition();
+        }
+    }
+
+    /**
+     * Enables entrance transition.<p>
+     * Entrance transition is the standard slide-in transition that shows rows of data in
+     * browse screen and details screen.
+     * <p>
+     * The method is ignored before LOLLIPOP (API21).
+     * <p>
+     * This method must be called in or
+     * before onCreate().  Typically entrance transition should be enabled when savedInstance is
+     * null so that fragment restored from instanceState does not run an extra entrance transition.
+     * When the entrance transition is enabled, the fragment will make headers and content
+     * hidden initially.
+     * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
+     * the transition, otherwise the rows will be invisible forever.
+     * <p>
+     * It is similar to android:windowsEnterTransition and can be considered a late-executed
+     * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
+     * <li> Workaround the problem that activity transition is not available between launcher and
+     * app.  Browse activity must programmatically start the slide-in transition.</li>
+     * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
+     * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
+     * to be loaded.</li>
+     * <p>
+     * Transition object is returned by createEntranceTransition().  Typically the app does not need
+     * override the default transition that browse and details provides.
+     */
+    public void prepareEntranceTransition() {
+        if (TransitionHelper.systemSupportsEntranceTransitions()) {
+            mEntranceTransitionEnabled = true;
+        }
+    }
+
+    /**
+     * Return true if entrance transition is enabled and not started yet.
+     * Entrance transition can only be executed once and isEntranceTransitionEnabled()
+     * is reset to false after entrance transition is started.
+     */
+    boolean isEntranceTransitionEnabled() {
+        return mEntranceTransitionEnabled;
+    }
+
+    /**
+     * Create entrance transition.  Subclass can override to load transition from
+     * resource or construct manually.  Typically app does not need to
+     * override the default transition that browse and details provides.
+     */
+    protected Object createEntranceTransition() {
+        return null;
+    }
+
+    /**
+     * Run entrance transition.  Subclass may use TransitionManager to perform
+     * go(Scene) or beginDelayedTransition().  App should not override the default
+     * implementation of browse and details fragment.
+     */
+    protected void runEntranceTransition(Object entranceTransition) {
+    }
+
+    /**
+     * Callback when entrance transition is started.
+     */
+    protected void onEntranceTransitionStart() {
+    }
+
+    /**
+     * Callback when entrance transition is ended.
+     */
+    protected void onEntranceTransitionEnd() {
+    }
+
+    /**
+     * When fragment finishes loading data, it should call startEntranceTransition()
+     * to execute the entrance transition.
+     * startEntranceTransition() will start transition only if both two conditions
+     * are satisfied:
+     * <li> prepareEntranceTransition() was called.</li>
+     * <li> has not executed entrance transition yet.</li>
+     * <p>
+     * If startEntranceTransition() is called before onViewCreated(), it will be pending
+     * and executed when view is created.
+     */
+    public void startEntranceTransition() {
+        if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
+            return;
+        }
+        // if view is not created yet, delay until onViewCreated()
+        if (getView() == null) {
+            mStartEntranceTransitionPending = true;
+            return;
+        }
+        // wait till views get their initial position before start transition
+        final View view = getView();
+        view.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                view.getViewTreeObserver().removeOnPreDrawListener(this);
+                internalCreateEntranceTransition();
+                mEntranceTransitionEnabled = false;
+                runEntranceTransition(mEntranceTransition);
+                return false;
+            }
+        });
+        view.invalidate();
+    }
+
+    void internalCreateEntranceTransition() {
+        mEntranceTransition = createEntranceTransition();
+        if (mEntranceTransition == null) {
+            return;
+        }
+        sTransitionHelper.setTransitionListener(mEntranceTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+                onEntranceTransitionStart();
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mEntranceTransition = null;
+                onEntranceTransitionEnd();
+            }
+        });
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
index 74778e3..48b81a6 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -35,10 +35,8 @@
     private PresenterSelector mPresenterSelector;
     private ItemBridgeAdapter mBridgeAdapter;
     private int mSelectedPosition = -1;
-    protected int mReparentHeaderId;
-    protected boolean mInTransition;
 
-    abstract protected int getLayoutResourceId();
+    abstract int getLayoutResourceId();
 
     private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
         @Override
@@ -47,7 +45,7 @@
         }
     };
 
-    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
     }
 
     @Override
@@ -58,7 +56,7 @@
         return view;
     }
 
-    protected VerticalGridView findGridViewFromRoot(View view) {
+    VerticalGridView findGridViewFromRoot(View view) {
         return (VerticalGridView) view;
     }
 
@@ -112,17 +110,28 @@
     /**
      * Returns the bridge adapter.
      */
-    protected final ItemBridgeAdapter getBridgeAdapter() {
+    final ItemBridgeAdapter getBridgeAdapter() {
         return mBridgeAdapter;
     }
 
     /**
-     * Set the selected item position.
+     * Sets the selected row position with smooth animation.
      */
     public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
         mSelectedPosition = position;
         if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
-            mVerticalGridView.setSelectedPositionSmooth(position);
+            if (smooth) {
+                mVerticalGridView.setSelectedPositionSmooth(position);
+            } else {
+                mVerticalGridView.setSelectedPosition(position);
+            }
         }
     }
 
@@ -130,7 +139,7 @@
         return mVerticalGridView;
     }
 
-    protected void updateAdapter() {
+    void updateAdapter() {
         mBridgeAdapter = null;
 
         if (mAdapter != null) {
@@ -145,7 +154,7 @@
         }
     }
 
-    protected Object getItem(Row row, int position) {
+    Object getItem(Row row, int position) {
         if (row instanceof ListRow) {
             return ((ListRow) row).getAdapter().get(position);
         } else {
@@ -153,12 +162,7 @@
         }
     }
 
-    void setReparentHeaderId(int reparentId) {
-        mReparentHeaderId = reparentId;
-    }
-
     void onTransitionStart() {
-        mInTransition = true;
         if (mVerticalGridView != null) {
             mVerticalGridView.setAnimateChildLayout(false);
             mVerticalGridView.setPruneChild(false);
@@ -172,7 +176,6 @@
             mVerticalGridView.setPruneChild(true);
             mVerticalGridView.setFocusSearchDisabled(false);
         }
-        mInTransition = false;
     }
 
     void setItemAlignment() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
new file mode 100644
index 0000000..08434fd
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -0,0 +1,201 @@
+/* This file is auto-generated from BaseRowFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ */
+abstract class BaseRowSupportFragment extends Fragment {
+    private ObjectAdapter mAdapter;
+    private VerticalGridView mVerticalGridView;
+    private PresenterSelector mPresenterSelector;
+    private ItemBridgeAdapter mBridgeAdapter;
+    private int mSelectedPosition = -1;
+
+    abstract int getLayoutResourceId();
+
+    private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
+        @Override
+        public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+            onRowSelected(parent, view, position, id);
+        }
+    };
+
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(getLayoutResourceId(), container, false);
+        mVerticalGridView = findGridViewFromRoot(view);
+        return view;
+    }
+
+    VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (mBridgeAdapter != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+        mVerticalGridView.setOnChildSelectedListener(mRowSelectedListener);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mVerticalGridView = null;
+    }
+
+    /**
+     * Set the presenter selector used to create and bind views.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        mPresenterSelector = presenterSelector;
+        updateAdapter();
+    }
+
+    /**
+     * Get the presenter selector used to create and bind views.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Sets the adapter for the fragment.
+     */
+    public final void setAdapter(ObjectAdapter rowsAdapter) {
+        mAdapter = rowsAdapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Returns the bridge adapter.
+     */
+    final ItemBridgeAdapter getBridgeAdapter() {
+        return mBridgeAdapter;
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSelectedPosition = position;
+        if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+            if (smooth) {
+                mVerticalGridView.setSelectedPositionSmooth(position);
+            } else {
+                mVerticalGridView.setSelectedPosition(position);
+            }
+        }
+    }
+
+    final VerticalGridView getVerticalGridView() {
+        return mVerticalGridView;
+    }
+
+    void updateAdapter() {
+        mBridgeAdapter = null;
+
+        if (mAdapter != null) {
+            // If presenter selector is null, adapter ps will be used
+            mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
+        }
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mBridgeAdapter != null && mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+
+    Object getItem(Row row, int position) {
+        if (row instanceof ListRow) {
+            return ((ListRow) row).getAdapter().get(position);
+        } else {
+            return null;
+        }
+    }
+
+    void onTransitionStart() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(false);
+            mVerticalGridView.setPruneChild(false);
+            mVerticalGridView.setFocusSearchDisabled(true);
+        }
+    }
+
+    void onTransitionEnd() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(true);
+            mVerticalGridView.setPruneChild(true);
+            mVerticalGridView.setFocusSearchDisabled(false);
+        }
+    }
+
+    void setItemAlignment() {
+        if (mVerticalGridView != null) {
+            // align the top edge of item
+            mVerticalGridView.setItemAlignmentOffset(0);
+            mVerticalGridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        }
+    }
+
+    void setWindowAlignmentFromTop(int alignedTop) {
+        if (mVerticalGridView != null) {
+            // align to a fixed position from top
+            mVerticalGridView.setWindowAlignmentOffset(alignedTop);
+            mVerticalGridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
new file mode 100644
index 0000000..88439ef
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -0,0 +1,169 @@
+/* This file is auto-generated from BaseFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * @hide
+ */
+class BaseSupportFragment extends Fragment {
+
+    private boolean mEntranceTransitionEnabled = false;
+    private boolean mStartEntranceTransitionPending = false;
+    private Object mEntranceTransition;
+
+    static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        if (mStartEntranceTransitionPending) {
+            mStartEntranceTransitionPending = false;
+            startEntranceTransition();
+        }
+    }
+
+    /**
+     * Enables entrance transition.<p>
+     * Entrance transition is the standard slide-in transition that shows rows of data in
+     * browse screen and details screen.
+     * <p>
+     * The method is ignored before LOLLIPOP (API21).
+     * <p>
+     * This method must be called in or
+     * before onCreate().  Typically entrance transition should be enabled when savedInstance is
+     * null so that fragment restored from instanceState does not run an extra entrance transition.
+     * When the entrance transition is enabled, the fragment will make headers and content
+     * hidden initially.
+     * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
+     * the transition, otherwise the rows will be invisible forever.
+     * <p>
+     * It is similar to android:windowsEnterTransition and can be considered a late-executed
+     * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
+     * <li> Workaround the problem that activity transition is not available between launcher and
+     * app.  Browse activity must programmatically start the slide-in transition.</li>
+     * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
+     * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
+     * to be loaded.</li>
+     * <p>
+     * Transition object is returned by createEntranceTransition().  Typically the app does not need
+     * override the default transition that browse and details provides.
+     */
+    public void prepareEntranceTransition() {
+        if (TransitionHelper.systemSupportsEntranceTransitions()) {
+            mEntranceTransitionEnabled = true;
+        }
+    }
+
+    /**
+     * Return true if entrance transition is enabled and not started yet.
+     * Entrance transition can only be executed once and isEntranceTransitionEnabled()
+     * is reset to false after entrance transition is started.
+     */
+    boolean isEntranceTransitionEnabled() {
+        return mEntranceTransitionEnabled;
+    }
+
+    /**
+     * Create entrance transition.  Subclass can override to load transition from
+     * resource or construct manually.  Typically app does not need to
+     * override the default transition that browse and details provides.
+     */
+    protected Object createEntranceTransition() {
+        return null;
+    }
+
+    /**
+     * Run entrance transition.  Subclass may use TransitionManager to perform
+     * go(Scene) or beginDelayedTransition().  App should not override the default
+     * implementation of browse and details fragment.
+     */
+    protected void runEntranceTransition(Object entranceTransition) {
+    }
+
+    /**
+     * Callback when entrance transition is started.
+     */
+    protected void onEntranceTransitionStart() {
+    }
+
+    /**
+     * Callback when entrance transition is ended.
+     */
+    protected void onEntranceTransitionEnd() {
+    }
+
+    /**
+     * When fragment finishes loading data, it should call startEntranceTransition()
+     * to execute the entrance transition.
+     * startEntranceTransition() will start transition only if both two conditions
+     * are satisfied:
+     * <li> prepareEntranceTransition() was called.</li>
+     * <li> has not executed entrance transition yet.</li>
+     * <p>
+     * If startEntranceTransition() is called before onViewCreated(), it will be pending
+     * and executed when view is created.
+     */
+    public void startEntranceTransition() {
+        if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
+            return;
+        }
+        // if view is not created yet, delay until onViewCreated()
+        if (getView() == null) {
+            mStartEntranceTransitionPending = true;
+            return;
+        }
+        // wait till views get their initial position before start transition
+        final View view = getView();
+        view.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                view.getViewTreeObserver().removeOnPreDrawListener(this);
+                internalCreateEntranceTransition();
+                mEntranceTransitionEnabled = false;
+                runEntranceTransition(mEntranceTransition);
+                return false;
+            }
+        });
+        view.invalidate();
+    }
+
+    void internalCreateEntranceTransition() {
+        mEntranceTransition = createEntranceTransition();
+        if (mEntranceTransition == null) {
+            return;
+        }
+        sTransitionHelper.setTransitionListener(mEntranceTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+                onEntranceTransitionStart();
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mEntranceTransition = null;
+                onEntranceTransitionEnd();
+            }
+        });
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 1978277..7e81dce 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -14,8 +14,10 @@
 package android.support.v17.leanback.app;
 
 import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.LeanbackTransitionHelper;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
 import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -30,6 +32,7 @@
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
 import android.util.Log;
 import android.app.Activity;
 import android.app.Fragment;
@@ -43,9 +46,11 @@
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 
 /**
@@ -61,12 +66,12 @@
  * <p>
  * By default the BrowseFragment includes support for returning to the headers
  * when the user presses Back. For Activities that customize {@link
- * Activity#onBackPressed()}, you must disable this default Back key support by
+ * android.app.Activity#onBackPressed()}, you must disable this default Back key support by
  * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
  * use {@link BrowseFragment.BrowseTransitionListener} and
  * {@link #startHeadersTransition(boolean)}.
  */
-public class BrowseFragment extends Fragment {
+public class BrowseFragment extends BaseFragment {
 
     // BUNDLE attribute for saving header show/hide status when backstack is used:
     static final String HEADER_STACK_INDEX = "headerStackIndex";
@@ -150,6 +155,47 @@
         }
     }
 
+    private class SetSelectionRunnable implements Runnable {
+        static final int TYPE_INVALID = -1;
+        static final int TYPE_INTERNAL_SYNC = 0;
+        static final int TYPE_USER_REQUEST = 1;
+
+        private int mPosition;
+        private int mType;
+        private boolean mSmooth;
+
+        SetSelectionRunnable() {
+            reset();
+        }
+
+        void post(int position, int type, boolean smooth) {
+            // Posting the set selection, rather than calling it immediately, prevents an issue
+            // with adapter changes.  Example: a row is added before the current selected row;
+            // first the fast lane view updates its selection, then the rows fragment has that
+            // new selection propagated immediately; THEN the rows view processes the same adapter
+            // change and moves the selection again.
+            if (type >= mType) {
+                mPosition = position;
+                mType = type;
+                mSmooth = smooth;
+                mBrowseFrame.removeCallbacks(this);
+                mBrowseFrame.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            setSelection(mPosition, mSmooth);
+            reset();
+        }
+
+        private void reset() {
+            mPosition = -1;
+            mType = TYPE_INVALID;
+            mSmooth = false;
+        }
+    }
+
     private static final String TAG = "BrowseFragment";
 
     private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
@@ -185,7 +231,7 @@
     private String mWithHeadersBackStackName;
     private boolean mShowingHeaders = true;
     private boolean mCanShowHeaders = true;
-    private int mContainerListMarginLeft;
+    private int mContainerListMarginStart;
     private int mContainerListAlignTop;
     private boolean mRowScaleEnabled = true;
     private SearchOrbView.Colors mSearchAffordanceColors;
@@ -198,19 +244,17 @@
     private int mSelectedPosition = -1;
 
     private PresenterSelector mHeaderPresenterSelector;
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
 
     // transition related:
-    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
-    private int mReparentHeaderId = View.generateViewId();
     private Object mSceneWithTitle;
     private Object mSceneWithoutTitle;
     private Object mSceneWithHeaders;
     private Object mSceneWithoutHeaders;
+    private Object mSceneAfterEntranceTransition;
     private Object mTitleUpTransition;
     private Object mTitleDownTransition;
     private Object mHeadersTransition;
-    private int mHeadersTransitionStartDelay;
-    private int mHeadersTransitionDuration;
     private BackStackListener mBackStackChangedListener;
     private BrowseTransitionListener mBrowseTransitionListener;
 
@@ -521,31 +565,39 @@
             new BrowseFrameLayout.OnFocusSearchListener() {
         @Override
         public View onFocusSearch(View focused, int direction) {
-            // If headers fragment is disabled, just return null.
-            if (!mCanShowHeaders) return null;
+            // if headers is running transition,  focus stays
+            if (mCanShowHeaders && isInHeadersTransition()) {
+                return focused;
+            }
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
 
             final View searchOrbView = mTitleView.getSearchAffordanceView();
-            // if headers is running transition,  focus stays
-            if (isInHeadersTransition()) return focused;
-            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
-            if (direction == View.FOCUS_LEFT) {
+            if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
+                return mCanShowHeaders && mShowingHeaders ?
+                        mHeadersFragment.getVerticalGridView() :
+                        mRowsFragment.getVerticalGridView();
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+            }
+
+            // If headers fragment is disabled, just return null.
+            if (!mCanShowHeaders) {
+                return null;
+            }
+            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+            if (direction == towardStart) {
                 if (isVerticalScrolling() || mShowingHeaders) {
                     return focused;
                 }
                 return mHeadersFragment.getVerticalGridView();
-            } else if (direction == View.FOCUS_RIGHT) {
+            } else if (direction == towardEnd) {
                 if (isVerticalScrolling() || !mShowingHeaders) {
                     return focused;
                 }
                 return mRowsFragment.getVerticalGridView();
-            } else if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
-                return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
-                    mRowsFragment.getVerticalGridView();
-
-            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
-                    && direction == View.FOCUS_UP) {
-                return searchOrbView;
-
             } else {
                 return null;
             }
@@ -557,22 +609,34 @@
 
         @Override
         public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return true;
+            }
             // Make sure not changing focus when requestFocus() is called.
             if (mCanShowHeaders && mShowingHeaders) {
-                if (mHeadersFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                if (mHeadersFragment != null && mHeadersFragment.getView() != null &&
+                        mHeadersFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                     return true;
                 }
             }
-            if (mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+            if (mRowsFragment != null && mRowsFragment.getView() != null &&
+                    mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                 return true;
             }
-            return mTitleView.requestFocus(direction, previouslyFocusedRect);
+            if (mTitleView != null &&
+                    mTitleView.requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            return false;
         };
 
         @Override
         public void onRequestChildFocus(View child, View focused) {
-            int childId = child.getId();
+            if (getChildFragmentManager().isDestroyed()) {
+                return;
+            }
             if (!mCanShowHeaders || isInHeadersTransition()) return;
+            int childId = child.getId();
             if (childId == R.id.browse_container_dock && mShowingHeaders) {
                 startHeadersTransitionInternal(false);
             } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
@@ -595,19 +659,27 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
-        mContainerListMarginLeft = (int) ta.getDimension(
+        mContainerListMarginStart = (int) ta.getDimension(
                 R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
         mContainerListAlignTop = (int) ta.getDimension(
                 R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
         ta.recycle();
 
-        mHeadersTransitionStartDelay = getResources()
-                .getInteger(R.integer.lb_browse_headers_transition_delay);
-        mHeadersTransitionDuration = getResources()
-                .getInteger(R.integer.lb_browse_headers_transition_duration);
-
         readArguments(getArguments());
 
+        if (mCanShowHeaders) {
+            if (mHeadersBackStackEnabled) {
+                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+                mBackStackChangedListener = new BackStackListener();
+                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+                mBackStackChangedListener.load(savedInstanceState);
+            } else {
+                if (savedInstanceState != null) {
+                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+                }
+            }
+        }
+
     }
 
     @Override
@@ -694,26 +766,18 @@
                 showHeaders(false);
             }
         });
-        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
-        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
-
-        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true);
-        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true);
-        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.container_list, true);
-        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.container_list, true);
-
-        if (mCanShowHeaders) {
-            if (mHeadersBackStackEnabled) {
-                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
-                mBackStackChangedListener = new BackStackListener();
-                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
-                mBackStackChangedListener.load(savedInstanceState);
-            } else {
-                if (savedInstanceState != null) {
-                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
-                }
+        mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionEndState();
             }
-        }
+        });
+        Context context = getActivity();
+        mTitleUpTransition = LeanbackTransitionHelper.loadTitleOutTransition(context,
+                sTransitionHelper);
+        mTitleDownTransition = LeanbackTransitionHelper.loadTitleInTransition(context,
+                sTransitionHelper);
+
         if (savedInstanceState != null) {
             mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
         }
@@ -723,40 +787,9 @@
     }
 
     private void createHeadersTransition() {
-        mHeadersTransition = sTransitionHelper.createTransitionSet(false);
-        sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
-        Object changeBounds = sTransitionHelper.createChangeBounds(false);
-        Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
-        Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
-        Object scale = sTransitionHelper.createScale();
-        if (TransitionHelper.systemSupportsTransitions()) {
-            Context context = getView().getContext();
-            sTransitionHelper.setInterpolator(changeBounds,
-                    sTransitionHelper.createDefaultInterpolator(context));
-            sTransitionHelper.setInterpolator(fadeIn,
-                    sTransitionHelper.createDefaultInterpolator(context));
-            sTransitionHelper.setInterpolator(fadeOut,
-                    sTransitionHelper.createDefaultInterpolator(context));
-            sTransitionHelper.setInterpolator(scale,
-                    sTransitionHelper.createDefaultInterpolator(context));
-        }
-
-        sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration);
-        sTransitionHelper.addTransition(mHeadersTransition, fadeOut);
-
-        if (mShowingHeaders) {
-            sTransitionHelper.setStartDelay(changeBounds, mHeadersTransitionStartDelay);
-            sTransitionHelper.setStartDelay(scale, mHeadersTransitionStartDelay);
-        }
-        sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration);
-        sTransitionHelper.addTransition(mHeadersTransition, changeBounds);
-        sTransitionHelper.addTarget(scale, mRowsFragment.getVerticalGridView());
-        sTransitionHelper.setDuration(scale, mHeadersTransitionDuration);
-        sTransitionHelper.addTransition(mHeadersTransition, scale);
-
-        sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration);
-        sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay);
-        sTransitionHelper.addTransition(mHeadersTransition, fadeIn);
+        mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
+                mShowingHeaders ?
+                R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
 
         sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
             @Override
@@ -798,22 +831,29 @@
         }
     }
 
+    private void setRowsAlignedLeft(boolean alignLeft) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mRowsFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
+    private void setHeadersOnScreen(boolean onScreen) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mHeadersFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
     private void showHeaders(boolean show) {
         if (DEBUG) Log.v(TAG, "showHeaders " + show);
         mHeadersFragment.setHeadersEnabled(show);
-        MarginLayoutParams lp;
-        View containerList;
-
-        containerList = mRowsFragment.getView();
-        lp = (MarginLayoutParams) containerList.getLayoutParams();
-        lp.leftMargin = show ? mContainerListMarginLeft : 0;
-        containerList.setLayoutParams(lp);
-
-        containerList = mHeadersFragment.getView();
-        lp = (MarginLayoutParams) containerList.getLayoutParams();
-        lp.leftMargin = show ? 0 : -mContainerListMarginLeft;
-        containerList.setLayoutParams(lp);
-
+        setHeadersOnScreen(show);
+        setRowsAlignedLeft(!show);
         mRowsFragment.setExpand(!show);
     }
 
@@ -863,8 +903,8 @@
 
     private void onRowSelected(int position) {
         if (position != mSelectedPosition) {
-            mSetSelectionRunnable.mPosition = position;
-            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+            mSetSelectionRunnable.post(
+                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
 
             if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
                 if (!mShowingTitle) {
@@ -878,24 +918,29 @@
         }
     }
 
-    private class SetSelectionRunnable implements Runnable {
-        int mPosition;
-        @Override
-        public void run() {
-            setSelection(mPosition);
-        }
-    }
-
-    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
-
-    private void setSelection(int position) {
+    private void setSelection(int position, boolean smooth) {
         if (position != NO_POSITION) {
-            mRowsFragment.setSelectedPosition(position);
-            mHeadersFragment.setSelectedPosition(position);
+            mRowsFragment.setSelectedPosition(position, smooth);
+            mHeadersFragment.setSelectedPosition(position, smooth);
         }
         mSelectedPosition = position;
     }
 
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
+    }
+
     @Override
     public void onStart() {
         super.onStart();
@@ -904,8 +949,7 @@
         mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
         mRowsFragment.setItemAlignment();
 
-        mRowsFragment.getVerticalGridView().setPivotX(0);
-        mRowsFragment.getVerticalGridView().setPivotY(mContainerListAlignTop);
+        mRowsFragment.setScalePivots(0, mContainerListAlignTop);
 
         if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
             mHeadersFragment.getView().requestFocus();
@@ -916,6 +960,21 @@
         if (mCanShowHeaders) {
             showHeaders(mShowingHeaders);
         }
+        if (isEntranceTransitionEnabled()) {
+            setEntranceTransitionStartState();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        mTitleView.enableAnimation(false);
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mTitleView.enableAnimation(true);
     }
 
     /**
@@ -1036,5 +1095,49 @@
     public int getHeadersState() {
         return mHeadersState;
     }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return sTransitionHelper.loadTransition(getActivity(),
+                R.transition.lb_browse_entrance_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+                entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        mHeadersFragment.onTransitionStart();
+        mRowsFragment.onTransitionStart();
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        mRowsFragment.onTransitionEnd();
+        mHeadersFragment.onTransitionEnd();
+    }
+
+    void setSearchOrbViewOnScreen(boolean onScreen) {
+        View searchOrbView = mTitleView.getSearchAffordanceView();
+        MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        searchOrbView.setLayoutParams(lp);
+    }
+
+    void setEntranceTransitionStartState() {
+        setHeadersOnScreen(false);
+        setSearchOrbViewOnScreen(false);
+        mRowsFragment.setEntranceTransitionState(false);
+    }
+
+    void setEntranceTransitionEndState() {
+        setHeadersOnScreen(mShowingHeaders);
+        setSearchOrbViewOnScreen(true);
+        mRowsFragment.setEntranceTransitionState(true);
+    }
+
 }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
new file mode 100644
index 0000000..fd922fd
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -0,0 +1,1145 @@
+/* This file is auto-generated from BrowseFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.LeanbackTransitionHelper;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+/**
+ * A fragment for creating Leanback browse screens. It is composed of a
+ * RowsSupportFragment and a HeadersSupportFragment.
+ * <p>
+ * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list. The elements in this adapter must be subclasses
+ * of {@link Row}.
+ * <p>
+ * The HeadersSupportFragment can be set to be either shown or hidden by default, or
+ * may be disabled entirely. See {@link #setHeadersState} for details.
+ * <p>
+ * By default the BrowseSupportFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
+ * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
+ * use {@link BrowseSupportFragment.BrowseTransitionListener} and
+ * {@link #startHeadersTransition(boolean)}.
+ */
+public class BrowseSupportFragment extends BaseSupportFragment {
+
+    // BUNDLE attribute for saving header show/hide status when backstack is used:
+    static final String HEADER_STACK_INDEX = "headerStackIndex";
+    // BUNDLE attribute for saving header show/hide status when backstack is not used:
+    static final String HEADER_SHOW = "headerShow";
+    // BUNDLE attribute for title is showing
+    static final String TITLE_SHOW = "titleShow";
+
+    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
+        int mLastEntryCount;
+        int mIndexOfHeadersBackStack;
+
+        BackStackListener() {
+            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
+            mIndexOfHeadersBackStack = -1;
+        }
+
+        void load(Bundle savedInstanceState) {
+            if (savedInstanceState != null) {
+                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
+                mShowingHeaders = mIndexOfHeadersBackStack == -1;
+            } else {
+                if (!mShowingHeaders) {
+                    getFragmentManager().beginTransaction()
+                            .addToBackStack(mWithHeadersBackStackName).commit();
+                }
+            }
+        }
+
+        void save(Bundle outState) {
+            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
+        }
+
+
+        @Override
+        public void onBackStackChanged() {
+            if (getFragmentManager() == null) {
+                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
+                return;
+            }
+            int count = getFragmentManager().getBackStackEntryCount();
+            // if backstack is growing and last pushed entry is "headers" backstack,
+            // remember the index of the entry.
+            if (count > mLastEntryCount) {
+                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+                if (mWithHeadersBackStackName.equals(entry.getName())) {
+                    mIndexOfHeadersBackStack = count - 1;
+                }
+            } else if (count < mLastEntryCount) {
+                // if popped "headers" backstack, initiate the show header transition if needed
+                if (mIndexOfHeadersBackStack >= count) {
+                    mIndexOfHeadersBackStack = -1;
+                    if (!mShowingHeaders) {
+                        startHeadersTransitionInternal(true);
+                    }
+                }
+            }
+            mLastEntryCount = count;
+        }
+    }
+
+    /**
+     * Listener for transitions between browse headers and rows.
+     */
+    public static class BrowseTransitionListener {
+        /**
+         * Callback when headers transition starts.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStart(boolean withHeaders) {
+        }
+        /**
+         * Callback when headers transition stops.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStop(boolean withHeaders) {
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        static final int TYPE_INVALID = -1;
+        static final int TYPE_INTERNAL_SYNC = 0;
+        static final int TYPE_USER_REQUEST = 1;
+
+        private int mPosition;
+        private int mType;
+        private boolean mSmooth;
+
+        SetSelectionRunnable() {
+            reset();
+        }
+
+        void post(int position, int type, boolean smooth) {
+            // Posting the set selection, rather than calling it immediately, prevents an issue
+            // with adapter changes.  Example: a row is added before the current selected row;
+            // first the fast lane view updates its selection, then the rows fragment has that
+            // new selection propagated immediately; THEN the rows view processes the same adapter
+            // change and moves the selection again.
+            if (type >= mType) {
+                mPosition = position;
+                mType = type;
+                mSmooth = smooth;
+                mBrowseFrame.removeCallbacks(this);
+                mBrowseFrame.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            setSelection(mPosition, mSmooth);
+            reset();
+        }
+
+        private void reset() {
+            mPosition = -1;
+            mType = TYPE_INVALID;
+            mSmooth = false;
+        }
+    }
+
+    private static final String TAG = "BrowseSupportFragment";
+
+    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
+
+    private static boolean DEBUG = false;
+
+    /** The headers fragment is enabled and shown by default. */
+    public static final int HEADERS_ENABLED = 1;
+
+    /** The headers fragment is enabled and hidden by default. */
+    public static final int HEADERS_HIDDEN = 2;
+
+    /** The headers fragment is disabled and will never be shown. */
+    public static final int HEADERS_DISABLED = 3;
+
+    private static final float SLIDE_DISTANCE_FACTOR = 2;
+
+    private RowsSupportFragment mRowsSupportFragment;
+    private HeadersSupportFragment mHeadersSupportFragment;
+
+    private ObjectAdapter mAdapter;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private int mHeadersState = HEADERS_ENABLED;
+    private int mBrandColor = Color.TRANSPARENT;
+    private boolean mBrandColorSet;
+
+    private BrowseFrameLayout mBrowseFrame;
+    private TitleView mTitleView;
+    private boolean mShowingTitle = true;
+    private boolean mHeadersBackStackEnabled = true;
+    private String mWithHeadersBackStackName;
+    private boolean mShowingHeaders = true;
+    private boolean mCanShowHeaders = true;
+    private int mContainerListMarginStart;
+    private int mContainerListAlignTop;
+    private boolean mRowScaleEnabled = true;
+    private SearchOrbView.Colors mSearchAffordanceColors;
+    private boolean mSearchAffordanceColorSet;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnClickListener mExternalOnSearchClickedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+
+    private PresenterSelector mHeaderPresenterSelector;
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    // transition related:
+    private Object mSceneWithTitle;
+    private Object mSceneWithoutTitle;
+    private Object mSceneWithHeaders;
+    private Object mSceneWithoutHeaders;
+    private Object mSceneAfterEntranceTransition;
+    private Object mTitleUpTransition;
+    private Object mTitleDownTransition;
+    private Object mHeadersTransition;
+    private BackStackListener mBackStackChangedListener;
+    private BrowseTransitionListener mBrowseTransitionListener;
+
+    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
+    private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge";
+    private static final String ARG_HEADERS_STATE =
+        BrowseSupportFragment.class.getCanonicalName() + ".headersState";
+
+    /**
+     * Create arguments for a browse fragment.
+     *
+     * @param args The Bundle to place arguments into, or null if the method
+     *        should return a new Bundle.
+     * @param title The title of the BrowseSupportFragment.
+     * @param headersState The initial state of the headers of the
+     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
+     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
+     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
+     */
+    public static Bundle createArgs(Bundle args, String title, int headersState) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_TITLE, title);
+        args.putInt(ARG_HEADERS_STATE, headersState);
+        return args;
+    }
+
+    /**
+     * Sets the brand color for the browse fragment. The brand color is used as
+     * the primary color for UI elements in the browse fragment. For example,
+     * the background color of the headers fragment uses the brand color.
+     *
+     * @param color The color to use as the brand color of the fragment.
+     */
+    public void setBrandColor(int color) {
+        mBrandColor = color;
+        mBrandColorSet = true;
+
+        if (mHeadersSupportFragment != null) {
+            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+        }
+    }
+
+    /**
+     * Returns the brand color for the browse fragment.
+     * The default is transparent.
+     */
+    public int getBrandColor() {
+        return mBrandColor;
+    }
+
+    /**
+     * Sets the adapter containing the rows for the fragment.
+     *
+     * <p>The items referenced by the adapter must be be derived from
+     * {@link Row}. These rows will be used by the rows fragment and the headers
+     * fragment (if not disabled) to render the browse rows.
+     *
+     * @param adapter An ObjectAdapter for the browse rows. All items must
+     *        derive from {@link Row}.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setAdapter(adapter);
+            mHeadersSupportFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the adapter containing the rows for the fragment.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener. This listener will be called when an
+     * item or row is selected by a user.
+     *
+     * @param listener The listener to call when an item or row is selected.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mExternalOnItemViewSelectedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     *
+     * <p>OnItemClickedListener will override {@link View.OnClickListener} that
+     * an item presenter may set during
+     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
+     * should choose to use an {@link OnItemClickedListener} or a
+     * {@link View.OnClickListener} on your item views, but not both.
+     *
+     * @param listener The listener to call when an item is clicked.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Sets a click listener for the search affordance.
+     *
+     * <p>The presence of a listener will change the visibility of the search
+     * affordance in the fragment title. When set to non-null, the title will
+     * contain an element that a user may click to begin a search.
+     *
+     * <p>The listener's {@link View.OnClickListener#onClick onClick} method
+     * will be invoked when the user clicks on the search element.
+     *
+     * @param listener The listener to call when the search element is clicked.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mTitleView != null) {
+            mTitleView.setOnSearchClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchAffordanceColors = colors;
+        mSearchAffordanceColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+    }
+
+    /**
+     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColors;
+        }
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColors();
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     * A default brighter color will be set by the framework.
+     *
+     * @param color The color to use for the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        setSearchAffordanceColors(new SearchOrbView.Colors(color));
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return getSearchAffordanceColors().color;
+    }
+
+    /**
+     * Start a headers transition.
+     *
+     * <p>This method will begin a transition to either show or hide the
+     * headers, depending on the value of withHeaders. If headers are disabled
+     * for this browse fragment, this method will throw an exception.
+     *
+     * @param withHeaders True if the headers should transition to being shown,
+     *        false if the transition should result in headers being hidden.
+     */
+    public void startHeadersTransition(boolean withHeaders) {
+        if (!mCanShowHeaders) {
+            throw new IllegalStateException("Cannot start headers transition");
+        }
+        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
+            return;
+        }
+        startHeadersTransitionInternal(withHeaders);
+    }
+
+    /**
+     * Returns true if the headers transition is currently running.
+     */
+    public boolean isInHeadersTransition() {
+        return mHeadersTransition != null;
+    }
+
+    /**
+     * Returns true if headers are shown.
+     */
+    public boolean isShowingHeaders() {
+        return mShowingHeaders;
+    }
+
+    /**
+     * Set a listener for browse fragment transitions.
+     *
+     * @param listener The listener to call when a browse headers transition
+     *        begins or ends.
+     */
+    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
+        mBrowseTransitionListener = listener;
+    }
+
+    /**
+     * Enables scaling of rows when headers are present.
+     * By default enabled to increase density.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableRowScaling(boolean enable) {
+        mRowScaleEnabled = enable;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
+        }
+    }
+
+    private void startHeadersTransitionInternal(final boolean withHeaders) {
+        if (getFragmentManager().isDestroyed()) {
+            return;
+        }
+        mShowingHeaders = withHeaders;
+        mRowsSupportFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+            @Override
+            public void run() {
+                mHeadersSupportFragment.onTransitionStart();
+                createHeadersTransition();
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
+                }
+                sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
+                        mHeadersTransition);
+                if (mHeadersBackStackEnabled) {
+                    if (!withHeaders) {
+                        getFragmentManager().beginTransaction()
+                                .addToBackStack(mWithHeadersBackStackName).commit();
+                    } else {
+                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
+                        if (index >= 0) {
+                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
+                            getFragmentManager().popBackStackImmediate(entry.getId(),
+                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    private boolean isVerticalScrolling() {
+        // don't run transition
+        return mHeadersSupportFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE
+                || mRowsSupportFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            // if headers is running transition,  focus stays
+            if (mCanShowHeaders && isInHeadersTransition()) {
+                return focused;
+            }
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
+            if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
+                return mCanShowHeaders && mShowingHeaders ?
+                        mHeadersSupportFragment.getVerticalGridView() :
+                        mRowsSupportFragment.getVerticalGridView();
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+            }
+
+            // If headers fragment is disabled, just return null.
+            if (!mCanShowHeaders) {
+                return null;
+            }
+            boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
+            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+            if (direction == towardStart) {
+                if (isVerticalScrolling() || mShowingHeaders) {
+                    return focused;
+                }
+                return mHeadersSupportFragment.getVerticalGridView();
+            } else if (direction == towardEnd) {
+                if (isVerticalScrolling() || !mShowingHeaders) {
+                    return focused;
+                }
+                return mRowsSupportFragment.getVerticalGridView();
+            } else {
+                return null;
+            }
+        }
+    };
+
+    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+            new BrowseFrameLayout.OnChildFocusListener() {
+
+        @Override
+        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return true;
+            }
+            // Make sure not changing focus when requestFocus() is called.
+            if (mCanShowHeaders && mShowingHeaders) {
+                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null &&
+                        mHeadersSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null &&
+                    mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            if (mTitleView != null &&
+                    mTitleView.requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            return false;
+        };
+
+        @Override
+        public void onRequestChildFocus(View child, View focused) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return;
+            }
+            if (!mCanShowHeaders || isInHeadersTransition()) return;
+            int childId = child.getId();
+            if (childId == R.id.browse_container_dock && mShowingHeaders) {
+                startHeadersTransitionInternal(false);
+            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+                startHeadersTransitionInternal(true);
+            }
+        }
+    };
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        if (mBackStackChangedListener != null) {
+            mBackStackChangedListener.save(outState);
+        } else {
+            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
+        }
+        outState.putBoolean(TITLE_SHOW, mShowingTitle);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
+        mContainerListMarginStart = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
+        mContainerListAlignTop = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
+        ta.recycle();
+
+        readArguments(getArguments());
+
+        if (mCanShowHeaders) {
+            if (mHeadersBackStackEnabled) {
+                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+                mBackStackChangedListener = new BackStackListener();
+                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+                mBackStackChangedListener.load(savedInstanceState);
+            } else {
+                if (savedInstanceState != null) {
+                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mBackStackChangedListener != null) {
+            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+            mRowsSupportFragment = new RowsSupportFragment();
+            mHeadersSupportFragment = new HeadersSupportFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
+                    .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
+        } else {
+            mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_headers_dock);
+            mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_container_dock);
+        }
+
+        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+
+        mRowsSupportFragment.setAdapter(mAdapter);
+        if (mHeaderPresenterSelector != null) {
+            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+        mHeadersSupportFragment.setAdapter(mAdapter);
+
+        mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
+        mRowsSupportFragment.setOnItemSelectedListener(mRowSelectedListener);
+        mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
+        mHeadersSupportFragment.setOnItemSelectedListener(mHeaderSelectedListener);
+        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+        mRowsSupportFragment.setOnItemClickedListener(mOnItemClickedListener);
+        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
+        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setTitle(mTitle);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+        if (mExternalOnSearchClickedListener != null) {
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+        }
+
+        if (mBrandColorSet) {
+            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+        }
+
+        mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                mTitleView.setVisibility(View.VISIBLE);
+            }
+        });
+        mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                mTitleView.setVisibility(View.INVISIBLE);
+            }
+        });
+        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(true);
+            }
+        });
+        mSceneWithoutHeaders =  sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(false);
+            }
+        });
+        mSceneAfterEntranceTransition = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionEndState();
+            }
+        });
+        Context context = getActivity();
+        mTitleUpTransition = LeanbackTransitionHelper.loadTitleOutTransition(context,
+                sTransitionHelper);
+        mTitleDownTransition = LeanbackTransitionHelper.loadTitleInTransition(context,
+                sTransitionHelper);
+
+        if (savedInstanceState != null) {
+            mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
+        }
+        mTitleView.setVisibility(mShowingTitle ? View.VISIBLE: View.INVISIBLE);
+
+        return root;
+    }
+
+    private void createHeadersTransition() {
+        mHeadersTransition = sTransitionHelper.loadTransition(getActivity(),
+                mShowingHeaders ?
+                R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
+
+        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mHeadersTransition = null;
+                mRowsSupportFragment.onTransitionEnd();
+                mHeadersSupportFragment.onTransitionEnd();
+                if (mShowingHeaders) {
+                    VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
+                    if (headerGridView != null && !headerGridView.hasFocus()) {
+                        headerGridView.requestFocus();
+                    }
+                } else {
+                    VerticalGridView rowsGridView = mRowsSupportFragment.getVerticalGridView();
+                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
+                        rowsGridView.requestFocus();
+                    }
+                }
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
+                }
+            }
+        });
+    }
+
+    /**
+     * Sets the {@link PresenterSelector} used to render the row headers.
+     *
+     * @param headerPresenterSelector The PresenterSelector that will determine
+     *        the Presenter for each row header.
+     */
+    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
+        mHeaderPresenterSelector = headerPresenterSelector;
+        if (mHeadersSupportFragment != null) {
+            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+    }
+
+    private void setRowsAlignedLeft(boolean alignLeft) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mRowsSupportFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
+    private void setHeadersOnScreen(boolean onScreen) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mHeadersSupportFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
+    private void showHeaders(boolean show) {
+        if (DEBUG) Log.v(TAG, "showHeaders " + show);
+        mHeadersSupportFragment.setHeadersEnabled(show);
+        setHeadersOnScreen(show);
+        setRowsAlignedLeft(!show);
+        mRowsSupportFragment.setExpand(!show);
+    }
+
+    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
+        new HeadersSupportFragment.OnHeaderClickedListener() {
+            @Override
+            public void onHeaderClicked() {
+                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
+                    return;
+                }
+                startHeadersTransitionInternal(false);
+                mRowsSupportFragment.getVerticalGridView().requestFocus();
+            }
+        };
+
+    private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            if (mExternalOnItemSelectedListener != null) {
+                mExternalOnItemSelectedListener.onItemSelected(item, row);
+            }
+        }
+    };
+
+    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "header selected position " + position);
+            onRowSelected(position);
+        }
+    };
+
+    private void onRowSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSetSelectionRunnable.post(
+                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
+
+            if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
+                if (!mShowingTitle) {
+                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+                    mShowingTitle = true;
+                }
+            } else if (mShowingTitle) {
+                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+                mShowingTitle = false;
+            }
+        }
+    }
+
+    private void setSelection(int position, boolean smooth) {
+        if (position != NO_POSITION) {
+            mRowsSupportFragment.setSelectedPosition(position, smooth);
+            mHeadersSupportFragment.setSelectedPosition(position, smooth);
+        }
+        mSelectedPosition = position;
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+        mHeadersSupportFragment.setItemAlignment();
+        mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+        mRowsSupportFragment.setItemAlignment();
+
+        mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
+
+        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
+            mHeadersSupportFragment.getView().requestFocus();
+        } else if ((!mCanShowHeaders || !mShowingHeaders)
+                && mRowsSupportFragment.getView() != null) {
+            mRowsSupportFragment.getView().requestFocus();
+        }
+        if (mCanShowHeaders) {
+            showHeaders(mShowingHeaders);
+        }
+        if (isEntranceTransitionEnabled()) {
+            setEntranceTransitionStartState();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        mTitleView.enableAnimation(false);
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mTitleView.enableAnimation(true);
+    }
+
+    /**
+     * Enable/disable headers transition on back key support. This is enabled by
+     * default. The BrowseSupportFragment will add a back stack entry when headers are
+     * showing. Running a headers transition when the back key is pressed only
+     * works when the headers state is {@link #HEADERS_ENABLED} or
+     * {@link #HEADERS_HIDDEN}.
+     * <p>
+     * NOTE: If an Activity has its own onBackPressed() handling, you must
+     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
+     * and {@link BrowseTransitionListener} in your own back stack handling.
+     */
+    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
+        mHeadersBackStackEnabled = headersBackStackEnabled;
+    }
+
+    /**
+     * Returns true if headers transition on back key support is enabled.
+     */
+    public final boolean isHeadersTransitionOnBackEnabled() {
+        return mHeadersBackStackEnabled;
+    }
+
+    private void readArguments(Bundle args) {
+        if (args == null) {
+            return;
+        }
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+        if (args.containsKey(ARG_HEADERS_STATE)) {
+            setHeadersState(args.getInt(ARG_HEADERS_STATE));
+        }
+    }
+
+    /**
+     * Sets the drawable displayed in the browse fragment title.
+     *
+     * @param drawable The Drawable to display in the browse fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (mBadgeDrawable != drawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the browse fragment.
+     *
+     * @param title The title of the browse fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title for the browse fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the state for the headers column in the browse fragment. Must be one
+     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
+     * {@link #HEADERS_DISABLED}.
+     *
+     * @param headersState The state of the headers for the browse fragment.
+     */
+    public void setHeadersState(int headersState) {
+        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+            throw new IllegalArgumentException("Invalid headers state: " + headersState);
+        }
+        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
+
+        if (headersState != mHeadersState) {
+            mHeadersState = headersState;
+            switch (headersState) {
+                case HEADERS_ENABLED:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = true;
+                    break;
+                case HEADERS_HIDDEN:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = false;
+                    break;
+                case HEADERS_DISABLED:
+                    mCanShowHeaders = false;
+                    mShowingHeaders = false;
+                    break;
+                default:
+                    Log.w(TAG, "Unknown headers state: " + headersState);
+                    break;
+            }
+            if (mHeadersSupportFragment != null) {
+                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+            }
+        }
+    }
+
+    /**
+     * Returns the state of the headers column in the browse fragment.
+     */
+    public int getHeadersState() {
+        return mHeadersState;
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return sTransitionHelper.loadTransition(getActivity(),
+                R.transition.lb_browse_entrance_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+                entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        mHeadersSupportFragment.onTransitionStart();
+        mRowsSupportFragment.onTransitionStart();
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        mRowsSupportFragment.onTransitionEnd();
+        mHeadersSupportFragment.onTransitionEnd();
+    }
+
+    void setSearchOrbViewOnScreen(boolean onScreen) {
+        View searchOrbView = mTitleView.getSearchAffordanceView();
+        MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        searchOrbView.setLayoutParams(lp);
+    }
+
+    void setEntranceTransitionStartState() {
+        setHeadersOnScreen(false);
+        setSearchOrbViewOnScreen(false);
+        mRowsSupportFragment.setEntranceTransitionState(false);
+    }
+
+    void setEntranceTransitionEndState() {
+        setHeadersOnScreen(mShowingHeaders);
+        setSearchOrbViewOnScreen(true);
+        mRowsSupportFragment.setEntranceTransitionState(true);
+    }
+
+}
+
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index 1cc278a..0c01c0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -32,10 +32,19 @@
 /**
  * Wrapper fragment for leanback details screens.
  */
-public class DetailsFragment extends Fragment {
+public class DetailsFragment extends BaseFragment {
     private static final String TAG = "DetailsFragment";
     private static boolean DEBUG = false;
 
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        boolean mSmooth = true;
+        @Override
+        public void run() {
+            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
+        }
+    }
+
     private RowsFragment mRowsFragment;
 
     private ObjectAdapter mAdapter;
@@ -46,6 +55,10 @@
     private OnItemViewClickedListener mOnItemViewClickedListener;
     private int mSelectedPosition = -1;
 
+    private Object mSceneAfterEntranceTransition;
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
     /**
      * Sets the list of rows for the fragment.
      */
@@ -138,6 +151,13 @@
         mRowsFragment.setOnItemViewSelectedListener(mExternalOnItemViewSelectedListener);
         mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
         mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        mSceneAfterEntranceTransition = sTransitionHelper.createScene((ViewGroup) view,
+                new Runnable() {
+            @Override
+            public void run() {
+                mRowsFragment.setEntranceTransitionState(true);
+            }
+        });
         return view;
     }
 
@@ -166,10 +186,51 @@
         setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
     }
 
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        if (getView() != null && getView().getHandler() != null) {
+            getView().getHandler().post(mSetSelectionRunnable);
+        }
+    }
+
     @Override
     public void onStart() {
         super.onStart();
         setupChildFragmentLayout();
         mRowsFragment.getView().requestFocus();
+        if (isEntranceTransitionEnabled()) {
+            // make sure recycler view animation is disabled
+            mRowsFragment.onTransitionStart();
+            mRowsFragment.setEntranceTransitionState(false);
+        }
     }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return sTransitionHelper.loadTransition(getActivity(),
+                R.transition.lb_details_enter_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+                entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        mRowsFragment.onTransitionEnd();
+    }
+
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
new file mode 100644
index 0000000..d8ae895b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -0,0 +1,238 @@
+/* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Wrapper fragment for leanback details screens.
+ */
+public class DetailsSupportFragment extends BaseSupportFragment {
+    private static final String TAG = "DetailsSupportFragment";
+    private static boolean DEBUG = false;
+
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        boolean mSmooth = true;
+        @Override
+        public void run() {
+            mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
+        }
+    }
+
+    private RowsSupportFragment mRowsSupportFragment;
+
+    private ObjectAdapter mAdapter;
+    private int mContainerListAlignTop;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+
+    private Object mSceneAfterEntranceTransition;
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item Clicked listener.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item Clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mRowsSupportFragment != null) {
+            mRowsSupportFragment.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mContainerListAlignTop =
+            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
+        mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
+                R.id.fragment_dock); 
+        if (mRowsSupportFragment == null) {
+            mRowsSupportFragment = new RowsSupportFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.fragment_dock, mRowsSupportFragment).commit();
+        }
+        mRowsSupportFragment.setAdapter(mAdapter);
+        mRowsSupportFragment.setOnItemSelectedListener(mExternalOnItemSelectedListener);
+        mRowsSupportFragment.setOnItemViewSelectedListener(mExternalOnItemViewSelectedListener);
+        mRowsSupportFragment.setOnItemClickedListener(mOnItemClickedListener);
+        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        mSceneAfterEntranceTransition = sTransitionHelper.createScene((ViewGroup) view,
+                new Runnable() {
+            @Override
+            public void run() {
+                mRowsSupportFragment.setEntranceTransitionState(true);
+            }
+        });
+        return view;
+    }
+
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        // align the top edge of item to a fixed position
+        listview.setItemAlignmentOffset(0);
+        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignmentOffset(mContainerListAlignTop);
+        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    VerticalGridView getVerticalGridView() {
+        return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
+    }
+
+    RowsSupportFragment getRowsSupportFragment() {
+        return mRowsSupportFragment;
+    }
+
+    /**
+     * Setup dimensions that are only meaningful when the child Fragments are inside
+     * DetailsSupportFragment.
+     */
+    private void setupChildFragmentLayout() {
+        setVerticalGridViewLayout(mRowsSupportFragment.getVerticalGridView());
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        if (getView() != null && getView().getHandler() != null) {
+            getView().getHandler().post(mSetSelectionRunnable);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupChildFragmentLayout();
+        mRowsSupportFragment.getView().requestFocus();
+        if (isEntranceTransitionEnabled()) {
+            // make sure recycler view animation is disabled
+            mRowsSupportFragment.onTransitionStart();
+            mRowsSupportFragment.setEntranceTransitionState(false);
+        }
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return sTransitionHelper.loadTransition(getActivity(),
+                R.transition.lb_details_enter_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        sTransitionHelper.runTransition(mSceneAfterEntranceTransition,
+                entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        mRowsSupportFragment.onTransitionEnd();
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
index cc7e560..a9f2a3e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
@@ -249,9 +249,6 @@
     private void updateMessage() {
         if (mTextView != null) {
             mTextView.setText(mMessage);
-            mTextView.setTextColor(mTextView.getResources().getColor(mIsBackgroundTranslucent ?
-                    R.color.lb_error_message_color_on_translucent :
-                    R.color.lb_error_message_color_on_opaque));
             mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
         }
     }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
new file mode 100644
index 0000000..2881921
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
@@ -0,0 +1,292 @@
+/* This file is auto-generated from ErrorFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.TitleView;
+import android.text.TextUtils;
+import android.util.Log;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A fragment for displaying an error indication.
+ */
+public class ErrorSupportFragment extends Fragment {
+
+    private View mErrorFrame;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private TitleView mTitleView;
+    private ImageView mImageView;
+    private TextView mTextView;
+    private Button mButton;
+    private Drawable mDrawable;
+    private CharSequence mMessage;
+    private String mButtonText;
+    private View.OnClickListener mButtonClickListener;
+    private Drawable mBackgroundDrawable;
+    private boolean mIsBackgroundTranslucent = true;
+
+    /**
+     * Sets the drawable displayed in the browse fragment title.
+     *
+     * @param drawable The drawable to display in the browse fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        updateTitle();
+    }
+
+    /**
+     * Returns the badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the browse fragment.
+     *
+     * @param title The title of the browse fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        updateTitle();
+    }
+
+    /**
+     * Returns the title for the browse fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the default background.
+     *
+     * @param translucent True to set a translucent background.
+     */
+    public void setDefaultBackground(boolean translucent) {
+        mBackgroundDrawable = null;
+        mIsBackgroundTranslucent = translucent;
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns true if the background is translucent.
+     */
+    public boolean isBackgroundTranslucent() {
+        return mIsBackgroundTranslucent;
+    }
+
+    /**
+     * Sets a drawable for the fragment background.
+     *
+     * @param drawable The drawable used for the background.
+     */
+    public void setBackgroundDrawable(Drawable drawable) {
+        mBackgroundDrawable = drawable;
+        if (drawable != null) {
+            final int opacity = drawable.getOpacity();
+            mIsBackgroundTranslucent = (opacity == PixelFormat.TRANSLUCENT ||
+                    opacity == PixelFormat.TRANSPARENT);
+        }
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns the background drawable.  May be null if a default is used.
+     */
+    public Drawable getBackgroundDrawable() {
+        return mBackgroundDrawable;
+    }
+
+    /**
+     * Sets the drawable to be used for the error image.
+     *
+     * @param drawable The drawable used for the error image.
+     */
+    public void setImageDrawable(Drawable drawable) {
+        mDrawable = drawable;
+        updateImageDrawable();
+    }
+
+    /**
+     * Returns the drawable used for the error image.
+     */
+    public Drawable getImageDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets the error message.
+     *
+     * @param message The error message.
+     */
+    public void setMessage(CharSequence message) {
+        mMessage = message;
+        updateMessage();
+    }
+
+    /**
+     * Returns the error message.
+     */
+    public CharSequence getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Sets the button text.
+     *
+     * @param text The button text.
+     */
+    public void setButtonText(String text) {
+        mButtonText = text;
+        updateButton();
+    }
+
+    /**
+     * Returns the button text.
+     */
+    public String getButtonText() {
+        return mButtonText;
+    }
+
+    /**
+     * Set the button click listener.
+     *
+     * @param clickListener The click listener for the button.
+     */
+    public void setButtonClickListener(View.OnClickListener clickListener) {
+        mButtonClickListener = clickListener;
+        updateButton();
+    }
+
+    /**
+     * Returns the button click listener.
+     */
+    public View.OnClickListener getButtonClickListener() {
+        return mButtonClickListener;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
+
+        mErrorFrame = root.findViewById(R.id.error_frame);
+        updateBackground();
+
+        mImageView = (ImageView) root.findViewById(R.id.image);
+        updateImageDrawable();
+
+        mTextView = (TextView) root.findViewById(R.id.message);
+        updateMessage();
+
+        mButton = (Button) root.findViewById(R.id.button);
+        updateButton();
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        updateTitle();
+
+        FontMetricsInt metrics = getFontMetricsInt(mTextView);
+        int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_image_baseline_margin);
+        setTopMargin(mTextView, underImageBaselineMargin + metrics.ascent);
+
+        int underMessageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_message_baseline_margin);
+        setTopMargin(mButton, underMessageBaselineMargin - metrics.descent);
+
+        return root;
+    }
+
+    private void updateBackground() {
+        if (mErrorFrame != null) {
+            if (mBackgroundDrawable != null) {
+                mErrorFrame.setBackground(mBackgroundDrawable);
+            } else {
+                mErrorFrame.setBackgroundColor(mErrorFrame.getResources().getColor(
+                        mIsBackgroundTranslucent ?
+                        R.color.lb_error_background_color_translucent :
+                        R.color.lb_error_background_color_opaque));
+            }
+        }
+    }
+
+    private void updateTitle() {
+        if (mTitleView != null) {
+            mTitleView.setTitle(mTitle);
+            mTitleView.setBadgeDrawable(mBadgeDrawable);
+        }
+    }
+
+    private void updateMessage() {
+        if (mTextView != null) {
+            mTextView.setText(mMessage);
+            mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateImageDrawable() {
+        if (mImageView != null) {
+            mImageView.setImageDrawable(mDrawable);
+            mImageView.setVisibility(mDrawable == null ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateButton() {
+        if (mButton != null) {
+            mButton.setText(mButtonText);
+            mButton.setOnClickListener(mButtonClickListener);
+            mButton.setVisibility(TextUtils.isEmpty(mButtonText) ? View.GONE : View.VISIBLE);
+            mButton.requestFocus();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mErrorFrame.requestFocus();
+    }
+
+    private static FontMetricsInt getFontMetricsInt(TextView textView) {
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setTextSize(textView.getTextSize());
+        paint.setTypeface(textView.getTypeface());
+        return paint.getFontMetricsInt();
+    }
+
+    private static void setTopMargin(TextView textView, int topMargin) {
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+        lp.topMargin = topMargin;
+        textView.setLayoutParams(lp);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
index 486ea0e..a637553 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -66,12 +66,12 @@
     }
 
     @Override
-    protected VerticalGridView findGridViewFromRoot(View view) {
+    VerticalGridView findGridViewFromRoot(View view) {
         return (VerticalGridView) view.findViewById(R.id.browse_headers);
     }
 
     @Override
-    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
         if (mOnItemSelectedListener != null) {
             if (position >= 0) {
                 Row row = (Row) getAdapter().get(position);
@@ -110,13 +110,13 @@
         @Override
         public void onLayoutChange(View v, int left, int top, int right, int bottom,
             int oldLeft, int oldTop, int oldRight, int oldBottom) {
-            v.setPivotX(0);
+            v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
             v.setPivotY(v.getMeasuredHeight() / 2);
         }
     };
 
     @Override
-    protected int getLayoutResourceId() {
+    int getLayoutResourceId() {
         return R.layout.lb_headers_fragment;
     }
 
@@ -188,7 +188,7 @@
         }
     };
     @Override
-    protected void updateAdapter() {
+    void updateAdapter() {
         super.updateAdapter();
         ItemBridgeAdapter adapter = getBridgeAdapter();
         if (adapter != null) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
new file mode 100644
index 0000000..229e5ad
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -0,0 +1,271 @@
+/* This file is auto-generated from HeadersFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.FrameLayout;
+
+/**
+ * An internal fragment containing a list of row headers.
+ */
+public class HeadersSupportFragment extends BaseRowSupportFragment {
+
+    interface OnHeaderClickedListener {
+        void onHeaderClicked();
+    }
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnHeaderClickedListener mOnHeaderClickedListener;
+    private boolean mHeadersEnabled = true;
+    private boolean mHeadersGone = false;
+    private int mBackgroundColor;
+    private boolean mBackgroundColorSet;
+
+    private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
+            new RowHeaderPresenter(R.layout.lb_header));
+
+    public HeadersSupportFragment() {
+        setPresenterSelector(sHeaderPresenter);
+    }
+
+    public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
+        mOnHeaderClickedListener = listener;
+    }
+
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    @Override
+    VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view.findViewById(R.id.browse_headers);
+    }
+
+    @Override
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        if (mOnItemSelectedListener != null) {
+            if (position >= 0) {
+                Row row = (Row) getAdapter().get(position);
+                mOnItemSelectedListener.onItemSelected(null, row);
+            } else {
+                mOnItemSelectedListener.onItemSelected(null, null);
+            }
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+            View headerView = viewHolder.getViewHolder().view;
+            headerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnHeaderClickedListener != null) {
+                        mOnHeaderClickedListener.onHeaderClicked();
+                    }
+                }
+            });
+            headerView.setFocusable(true);
+            headerView.setFocusableInTouchMode(true);
+            if (mWrapper != null) {
+                viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
+            } else {
+                headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+            }
+        }
+
+    };
+
+    private static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
+            v.setPivotY(v.getMeasuredHeight() / 2);
+        }
+    };
+
+    @Override
+    int getLayoutResourceId() {
+        return R.layout.lb_headers_fragment;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        if (getBridgeAdapter() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
+        }
+        view.setBackgroundColor(getBackgroundColor());
+        updateFadingEdgeToBrandColor(getBackgroundColor());
+        updateListViewVisibility();
+    }
+
+    private void updateListViewVisibility() {
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+            if (!mHeadersGone) {
+                if (mHeadersEnabled) {
+                    listView.setChildrenVisibility(View.VISIBLE);
+                } else {
+                    listView.setChildrenVisibility(View.INVISIBLE);
+                }
+            }
+        }
+    }
+
+    void setHeadersEnabled(boolean enabled) {
+        mHeadersEnabled = enabled;
+        updateListViewVisibility();
+    }
+
+    void setHeadersGone(boolean gone) {
+        mHeadersGone = gone;
+        updateListViewVisibility();
+    }
+
+    static class NoOverlappingFrameLayout extends FrameLayout {
+
+        public NoOverlappingFrameLayout(Context context) {
+            super(context);
+        }
+
+        /**
+         * Avoid creating hardware layer for header dock.
+         */
+        @Override
+        public boolean hasOverlappingRendering() {
+            return false;
+        }
+    }
+
+    // Wrapper needed because of conflict between RecyclerView's use of alpha
+    // for ADD animations, and RowHeaderPresnter's use of alpha for selected level.
+    private final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((FrameLayout) wrapper).addView(wrapped);
+        }
+
+        @Override
+        public View createWrapper(View root) {
+            return new NoOverlappingFrameLayout(root.getContext());
+        }
+    };
+    @Override
+    void updateAdapter() {
+        super.updateAdapter();
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mAdapterListener);
+            adapter.setWrapper(mWrapper);
+        }
+        if (adapter != null && getVerticalGridView() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
+        }
+    }
+
+    void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mBackgroundColorSet = true;
+
+        if (getView() != null) {
+            getView().setBackgroundColor(mBackgroundColor);
+            updateFadingEdgeToBrandColor(mBackgroundColor);
+        }
+    }
+
+    private void updateFadingEdgeToBrandColor(int backgroundColor) {
+        View fadingView = getView().findViewById(R.id.fade_out_edge);
+        Drawable background = fadingView.getBackground();
+        if (background instanceof GradientDrawable) {
+            background.mutate();
+            ((GradientDrawable) background).setColors(
+                    new int[] {Color.TRANSPARENT, backgroundColor});
+        }
+    }
+
+    int getBackgroundColor() {
+        if (getActivity() == null) {
+            throw new IllegalStateException("Activity must be attached");
+        }
+
+        if (mBackgroundColorSet) {
+            return mBackgroundColor;
+        }
+
+        TypedValue outValue = new TypedValue();
+        getActivity().getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
+        return getResources().getColor(outValue.resourceId);
+    }
+
+    @Override
+    void onTransitionStart() {
+        super.onTransitionStart();
+        if (!mHeadersEnabled) {
+            // When enabling headers fragment,  the RowHeaderView gets a focus but
+            // isShown() is still false because its parent is INVSIBILE, accessibility
+            // event is not sent.
+            // Workaround is: prevent focus to a child view during transition and put
+            // focus on it after transition is done.
+            final VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+                if (listView.hasFocus()) {
+                    listView.requestFocus();
+                }
+            }
+        }
+    }
+
+    @Override
+    void onTransitionEnd() {
+        if (mHeadersEnabled) {
+            final VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+                if (listView.hasFocus()) {
+                    listView.requestFocus();
+                }
+            }
+        }
+        super.onTransitionEnd();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
new file mode 100644
index 0000000..2f04229
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
@@ -0,0 +1,234 @@
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+/**
+ * A helper class for implementing a glue layer between a
+ * {@link PlaybackOverlayFragment} and a
+ * {@link android.support.v4.media.session.MediaControllerCompat}.
+ */
+public abstract class MediaControllerGlue extends PlaybackControlGlue {
+    private static final String TAG = "MediaControllerGlue";
+    private static final boolean DEBUG = false;
+
+    private MediaControllerCompat mMediaController;
+
+    private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            if (DEBUG) Log.v(TAG, "onMetadataChanged");
+            MediaControllerGlue.this.onMetadataChanged();
+        }
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
+            onStateChanged();
+        }
+        @Override
+        public void onSessionDestroyed() {
+            if (DEBUG) Log.v(TAG, "onSessionDestroyed");
+            mMediaController = null;
+        }
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            if (DEBUG) Log.v(TAG, "onSessionEvent");
+        }
+    };
+
+    /**
+     * Constructor for the glue.
+     *
+     * <p>The {@link PlaybackOverlayFragment} must be passed in.
+     * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
+     * {@link PlaybackOverlayFragment.InputEventHandler}
+     * will be set on the fragment.
+     * </p>
+     *
+     * @param context
+     * @param fragment
+     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+     */
+    public MediaControllerGlue(Context context,
+                               PlaybackOverlayFragment fragment,
+                               int[] seekSpeeds) {
+        super(context, fragment, seekSpeeds);
+    }
+
+    /**
+     * Constructor for the glue.
+     *
+     * <p>The {@link PlaybackOverlayFragment} must be passed in.
+     * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
+     * {@link PlaybackOverlayFragment.InputEventHandler}
+     * will be set on the fragment.
+     * </p>
+     *
+     * @param context
+     * @param fragment
+     * @param fastForwardSpeeds Array of seek speeds for fast forward.
+     * @param rewindSpeeds Array of seek speeds for rewind.
+     */
+    public MediaControllerGlue(Context context,
+                               PlaybackOverlayFragment fragment,
+                               int[] fastForwardSpeeds,
+                               int[] rewindSpeeds) {
+        super(context, fragment, fastForwardSpeeds, rewindSpeeds);
+    }
+
+    /**
+     * Attaches to the given media controller.
+     */
+    public void attachToMediaController(MediaControllerCompat mediaController) {
+        if (mediaController != mMediaController) {
+            if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
+            detach();
+            mMediaController = mediaController;
+            if (mMediaController != null) {
+                mMediaController.registerCallback(mCallback);
+            }
+            onMetadataChanged();
+            onStateChanged();
+        }
+    }
+
+    /**
+     * Detaches from the media controller.  Must be called when the object is no longer
+     * needed.
+     */
+    public void detach() {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mCallback);
+        }
+        mMediaController = null;
+    }
+
+    /**
+     * Returns the media controller currently attached.
+     */
+    public final MediaControllerCompat getMediaController() {
+        return mMediaController;
+    }
+
+    @Override
+    public boolean hasValidMedia() {
+        return mMediaController != null && mMediaController.getMetadata() != null;
+    }
+
+    @Override
+    public boolean isMediaPlaying() {
+        return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
+    }
+
+    @Override
+    public int getCurrentSpeedId() {
+        int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
+        if (speed == 0) {
+            return PLAYBACK_SPEED_PAUSED;
+        } else if (speed == 1) {
+            return PLAYBACK_SPEED_NORMAL;
+        } else if (speed > 0) {
+            int[] seekSpeeds = getFastForwardSpeeds();
+            for (int index = 0; index < seekSpeeds.length; index++) {
+                if (speed == seekSpeeds[index]) {
+                    return PLAYBACK_SPEED_FAST_L0 + index;
+                }
+            }
+        } else {
+            int[] seekSpeeds = getRewindSpeeds();
+            for (int index = 0; index < seekSpeeds.length; index++) {
+                if (-speed == seekSpeeds[index]) {
+                    return -PLAYBACK_SPEED_FAST_L0 - index;
+                }
+            }
+        }
+        Log.w(TAG, "Couldn't find index for speed " + speed);
+        return PLAYBACK_SPEED_INVALID;
+    }
+
+    @Override
+    public CharSequence getMediaTitle() {
+        return mMediaController.getMetadata().getDescription().getTitle();
+    }
+
+    @Override
+    public CharSequence getMediaSubtitle() {
+        return mMediaController.getMetadata().getDescription().getSubtitle();
+    }
+
+    @Override
+    public int getMediaDuration() {
+        return (int) mMediaController.getMetadata().getLong(
+                MediaMetadataCompat.METADATA_KEY_DURATION);
+    }
+
+    @Override
+    public int getCurrentPosition() {
+        return (int) mMediaController.getPlaybackState().getPosition();
+    }
+
+    @Override
+    public Drawable getMediaArt() {
+        Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
+        return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
+    }
+
+    @Override
+    public long getSupportedActions() {
+        long result = 0;
+        long actions = mMediaController.getPlaybackState().getActions();
+        if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
+            result |= ACTION_PLAY_PAUSE;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
+            result |= ACTION_SKIP_TO_NEXT;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
+            result |= ACTION_SKIP_TO_PREVIOUS;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
+            result |= ACTION_FAST_FORWARD;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
+            result |= ACTION_REWIND;
+        }
+        return result;
+    }
+
+    @Override
+    protected void startPlayback(int speed) {
+        if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
+        if (speed == PLAYBACK_SPEED_NORMAL) {
+            mMediaController.getTransportControls().play();
+        } else if (speed > 0) {
+            mMediaController.getTransportControls().fastForward();
+        } else {
+            mMediaController.getTransportControls().rewind();
+        }
+    }
+
+    @Override
+    protected void pausePlayback() {
+        if (DEBUG) Log.v(TAG, "pausePlayback");
+        mMediaController.getTransportControls().pause();
+    }
+
+    @Override
+    protected void skipToNext() {
+        if (DEBUG) Log.v(TAG, "skipToNext");
+        mMediaController.getTransportControls().skipToNext();
+    }
+
+    @Override
+    protected void skipToPrevious() {
+        if (DEBUG) Log.v(TAG, "skipToPrevious");
+        mMediaController.getTransportControls().skipToPrevious();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
new file mode 100644
index 0000000..41be7fc
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -0,0 +1,803 @@
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+
+
+/**
+ * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and
+ * {@link PlaybackOverlayFragment} that implements a recommended approach to handling standard
+ * playback control actions such as play/pause, fast forward/rewind at progressive speed levels,
+ * and skip to next/previous.  This helper class is a glue layer in that it manages the
+ * configuration of and interaction between the leanback UI components by defining a functional
+ * interface to the media player.
+ *
+ * <p>You can instantiate a concrete subclass such as {@link MediaControllerGlue} or you must
+ * subclass this abstract helper.  To create a subclass you must implement all of the
+ * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
+ * {@link #onStateChanged()} appropriately.
+ * </p>
+ *
+ * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
+ * inform the glue what speed levels are supported for fast forward/rewind.  If you have your own
+ * controls row you must pass it to {@link #setControlsRow}.  The row will be updated by the glue
+ * layer based on the media metadata and playback state.  Alternatively, you may call
+ * {@link #createControlsRowAndPresenter()} which will set a controls row and return
+ * a row presenter you can use to present the row.
+ * </p>
+ *
+ * <p>The helper sets a {@link android.support.v17.leanback.widget.SparseArrayObjectAdapter}
+ * on the controls row as the primary actions adapter, and adds actions to it.  You can provide
+ * additional actions by overriding {@link #createPrimaryActionsAdapter}.  This helper does not
+ * deal in secondary actions so those you may add separately.
+ * </p>
+ *
+ * <p>The helper sets an {@link android.support.v17.leanback.widget.OnItemViewClickedListener}
+ * on the fragment.  To receive callbacks on clicks for elements unknown to the helper, pass
+ * a listener to {@link #setOnItemViewClickedListener}.
+ * </p>
+ *
+ * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
+ * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
+ * {@link #getUpdatePeriod()} provides a recommended update period.
+ * </p>
+ *
+ */
+public abstract class PlaybackControlGlue {
+    /**
+     * The adapter key for the first custom control on the right side
+     * of the predefined primary controls.
+     */
+    public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+
+    /**
+     * The adapter key for the skip to previous control.
+     */
+    public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
+
+    /**
+     * The adapter key for the rewind control.
+     */
+    public static final int ACTION_REWIND = 0x20;
+
+    /**
+     * The adapter key for the play/pause control.
+     */
+    public static final int ACTION_PLAY_PAUSE = 0x40;
+
+    /**
+     * The adapter key for the fast forward control.
+     */
+    public static final int ACTION_FAST_FORWARD = 0x80;
+
+    /**
+     * The adapter key for the skip to next control.
+     */
+    public static final int ACTION_SKIP_TO_NEXT = 0x100;
+
+    /**
+     * The adapter key for the first custom control on the right side
+     * of the predefined primary controls.
+     */
+    public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
+
+    /**
+     * Invalid playback speed.
+     */
+    public static final int PLAYBACK_SPEED_INVALID = -1;
+
+    /**
+     * Speed representing playback state that is paused.
+     */
+    public static final int PLAYBACK_SPEED_PAUSED = 0;
+
+    /**
+     * Speed representing playback state that is playing normally.
+     */
+    public static final int PLAYBACK_SPEED_NORMAL = 1;
+
+    /**
+     * The initial (level 0) fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L0 = 10;
+
+    /**
+     * The level 1 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L1 = 11;
+
+    /**
+     * The level 2 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L2 = 12;
+
+    /**
+     * The level 3 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L3 = 13;
+
+    /**
+     * The level 4 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L4 = 14;
+
+    private static final String TAG = "PlaybackControlGlue";
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_UPDATE_PLAYBACK_STATE = 100;
+    private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
+    private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4 -
+            PLAYBACK_SPEED_FAST_L0 + 1;
+
+    private final PlaybackOverlayFragment mFragment;
+    private final Context mContext;
+    private final int[] mFastForwardSpeeds;
+    private final int[] mRewindSpeeds;
+    private PlaybackControlsRow mControlsRow;
+    private SparseArrayObjectAdapter mPrimaryActionsAdapter;
+    private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
+    private PlaybackControlsRow.SkipNextAction mSkipNextAction;
+    private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
+    private PlaybackControlsRow.FastForwardAction mFastForwardAction;
+    private PlaybackControlsRow.RewindAction mRewindAction;
+    private OnItemViewClickedListener mExternalOnItemViewClickedListener;
+    private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+    private boolean mFadeWhenPlaying = true;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
+                updatePlaybackState();
+            }
+        }
+    };
+
+    private final OnItemViewClickedListener mOnItemViewClickedListener =
+            new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder viewHolder, Object object,
+                                  RowPresenter.ViewHolder viewHolder2, Row row) {
+            if (DEBUG) Log.v(TAG, "onItemClicked " + object);
+            boolean handled = false;
+            if (object instanceof Action) {
+                handled = handleActionClicked((Action) object);
+            }
+            if (!handled && mExternalOnItemViewClickedListener != null) {
+                mExternalOnItemViewClickedListener.onItemClicked(viewHolder, object,
+                        viewHolder2, row);
+            }
+        }
+    };
+
+    private final PlaybackOverlayFragment.InputEventHandler mInputEventHandler =
+            new PlaybackOverlayFragment.InputEventHandler() {
+        @Override
+        public boolean handleInputEvent(InputEvent event) {
+            boolean result = false;
+            if (event instanceof KeyEvent &&
+                    ((KeyEvent) event).getAction() == KeyEvent.ACTION_DOWN) {
+                int keyCode = ((KeyEvent) event).getKeyCode();
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_UP:
+                    case KeyEvent.KEYCODE_DPAD_DOWN:
+                    case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    case KeyEvent.KEYCODE_DPAD_LEFT:
+                    case KeyEvent.KEYCODE_BACK:
+                        if (mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0 ||
+                                mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+                            mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+                            startPlayback(mPlaybackSpeed);
+                            updatePlaybackStatusAfterUserAction();
+                            result = (keyCode == KeyEvent.KEYCODE_BACK);
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+                        if (mPlayPauseAction != null) {
+                            handleActionClicked(mPlayPauseAction);
+                            result = true;
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+                        if (mFastForwardAction != null) {
+                            handleActionClicked(mFastForwardAction);
+                            result = true;
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_REWIND:
+                        if (mRewindAction != null) {
+                            handleActionClicked(mRewindAction);
+                            result = true;
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+                        if (mSkipPreviousAction != null) {
+                            handleActionClicked(mSkipPreviousAction);
+                            result = true;
+                        }
+                        break;
+                    case KeyEvent.KEYCODE_MEDIA_NEXT:
+                        if (mSkipNextAction != null) {
+                            handleActionClicked(mSkipNextAction);
+                            result = true;
+                        }
+                        break;
+                }
+            }
+            return result;
+        }
+    };
+
+    /**
+     * Constructor for the glue.
+     *
+     * <p>The {@link PlaybackOverlayFragment} must be passed in.
+     * A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler}
+     * will be set on the fragment.
+     * </p>
+     *
+     * @param context
+     * @param fragment
+     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+     */
+    public PlaybackControlGlue(Context context,
+                               PlaybackOverlayFragment fragment,
+                               int[] seekSpeeds) {
+        this(context, fragment, seekSpeeds, seekSpeeds);
+    }
+
+    /**
+     * Constructor for the glue.
+     *
+     * <p>The {@link PlaybackOverlayFragment} must be passed in.
+     * A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler}
+     * will be set on the fragment.
+     * </p>
+     *
+     * @param context
+     * @param fragment
+     * @param fastForwardSpeeds Array of seek speeds for fast forward.
+     * @param rewindSpeeds Array of seek speeds for rewind.
+     */
+    public PlaybackControlGlue(Context context,
+                               PlaybackOverlayFragment fragment,
+                               int[] fastForwardSpeeds,
+                               int[] rewindSpeeds) {
+        mContext = context;
+        mFragment = fragment;
+        if (mFragment.getOnItemViewClickedListener() != null) {
+            throw new IllegalStateException("Fragment OnItemViewClickedListener already present");
+        }
+        mFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        if (mFragment.getInputEventHandler() != null) {
+            throw new IllegalStateException("Fragment InputEventListener already present");
+        }
+        mFragment.setInputEventHandler(mInputEventHandler);
+        if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+            throw new IllegalStateException("invalid fastForwardSpeeds array size");
+        }
+        mFastForwardSpeeds = fastForwardSpeeds;
+        if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+            throw new IllegalStateException("invalid rewindSpeeds array size");
+        }
+        mRewindSpeeds = rewindSpeeds;
+    }
+
+    /**
+     * Helper method for instantiating a
+     * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
+     * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
+     */
+    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+        PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+        setControlsRow(controlsRow);
+
+        return new PlaybackControlsRowPresenter(new AbstractDetailsDescriptionPresenter() {
+            @Override
+            protected void onBindDescription(AbstractDetailsDescriptionPresenter.ViewHolder
+                                                     viewHolder, Object object) {
+                PlaybackControlGlue glue = (PlaybackControlGlue) object;
+                if (glue.hasValidMedia()) {
+                    viewHolder.getTitle().setText(glue.getMediaTitle());
+                    viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
+                } else {
+                    viewHolder.getTitle().setText("");
+                    viewHolder.getSubtitle().setText("");
+                }
+            }
+        });
+    }
+
+    /**
+     * Returns the fragment.
+     */
+    public PlaybackOverlayFragment getFragment() {
+        return mFragment;
+    }
+
+    /**
+     * Returns the context.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns the fast forward speeds.
+     */
+    public int[] getFastForwardSpeeds() {
+        return mFastForwardSpeeds;
+    }
+
+    /**
+     * Returns the rewind speeds.
+     */
+    public int[] getRewindSpeeds() {
+        return mRewindSpeeds;
+    }
+
+    /**
+     * Sets the controls to fade after a timeout when media is playing.
+     */
+    public void setFadingEnabled(boolean enable) {
+        mFadeWhenPlaying = enable;
+        if (!mFadeWhenPlaying) {
+            mFragment.setFadingEnabled(false);
+        }
+    }
+
+    /**
+     * Returns true if controls are set to fade when media is playing.
+     */
+    public boolean isFadingEnabled() {
+        return mFadeWhenPlaying;
+    }
+
+    /**
+     * Set the {@link OnItemViewClickedListener} to be called if the click event
+     * is not handled internally.
+     * @param listener
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mExternalOnItemViewClickedListener = listener;
+    }
+
+    /**
+     * Returns the {@link OnItemViewClickedListener}.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mExternalOnItemViewClickedListener;
+    }
+
+    /**
+     * Sets the controls row to be managed by the glue layer.
+     * The primary actions and playback state related aspects of the row
+     * are updated by the glue.
+     */
+    public void setControlsRow(PlaybackControlsRow controlsRow) {
+        mControlsRow = controlsRow;
+        mPrimaryActionsAdapter = createPrimaryActionsAdapter(
+                new ControlButtonPresenterSelector());
+        mControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
+        updateControlsRow();
+    }
+
+    /**
+     * Returns the playback controls row managed by the glue layer.
+     */
+    public PlaybackControlsRow getControlsRow() {
+        return mControlsRow;
+    }
+
+    /**
+     * Override this to start/stop a runnable to call {@link #updateProgress} at
+     * an interval such as {@link #getUpdatePeriod}.
+     */
+    public void enableProgressUpdating(boolean enable) {
+    }
+
+    /**
+     * Returns the time period in milliseconds that should be used
+     * to update the progress.  See {@link #updateProgress()}.
+     */
+    public int getUpdatePeriod() {
+        // TODO: calculate a better update period based on total duration and screen size
+        return 500;
+    }
+
+    /**
+     * Updates the progress bar based on the current media playback position.
+     */
+    public void updateProgress() {
+        int position = getCurrentPosition();
+        if (DEBUG) Log.v(TAG, "updateProgress " + position);
+        mControlsRow.setCurrentTime(position);
+    }
+
+    private boolean handleActionClicked(Action action) {
+        boolean handled = false;
+        if (action == mPlayPauseAction) {
+            if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+                mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+                startPlayback(mPlaybackSpeed);
+            } else {
+                mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
+                pausePlayback();
+            }
+            updatePlaybackStatusAfterUserAction();
+            handled = true;
+        } else if (action == mSkipNextAction) {
+            skipToNext();
+            handled = true;
+        } else if (action == mSkipPreviousAction) {
+            skipToPrevious();
+            handled = true;
+        } else if (action == mFastForwardAction) {
+            if (mPlaybackSpeed < getMaxForwardSpeedId()) {
+                switch (mPlaybackSpeed) {
+                    case PLAYBACK_SPEED_NORMAL:
+                    case PLAYBACK_SPEED_PAUSED:
+                        mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
+                        break;
+                    case PLAYBACK_SPEED_FAST_L0:
+                    case PLAYBACK_SPEED_FAST_L1:
+                    case PLAYBACK_SPEED_FAST_L2:
+                    case PLAYBACK_SPEED_FAST_L3:
+                        mPlaybackSpeed++;
+                        break;
+                }
+                startPlayback(mPlaybackSpeed);
+                updatePlaybackStatusAfterUserAction();
+            }
+            handled = true;
+        } else if (action == mRewindAction) {
+            if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
+                switch (mPlaybackSpeed) {
+                    case PLAYBACK_SPEED_NORMAL:
+                    case PLAYBACK_SPEED_PAUSED:
+                        mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
+                        break;
+                    case -PLAYBACK_SPEED_FAST_L0:
+                    case -PLAYBACK_SPEED_FAST_L1:
+                    case -PLAYBACK_SPEED_FAST_L2:
+                    case -PLAYBACK_SPEED_FAST_L3:
+                        mPlaybackSpeed--;
+                        break;
+                }
+                startPlayback(mPlaybackSpeed);
+                updatePlaybackStatusAfterUserAction();
+            }
+            handled = true;
+        }
+        return handled;
+    }
+
+    private int getMaxForwardSpeedId() {
+        return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
+    }
+
+    private int getMaxRewindSpeedId() {
+        return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
+    }
+
+    private void updateControlsRow() {
+        updateRowMetadata();
+        mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+        updatePlaybackState();
+    }
+
+    private void updatePlaybackStatusAfterUserAction() {
+        updatePlaybackState(mPlaybackSpeed);
+        // Sync playback state after a delay
+        mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
+                UPDATE_PLAYBACK_STATE_DELAY_MS);
+    }
+
+    private void updateRowMetadata() {
+        if (mControlsRow == null) {
+            return;
+        }
+
+        if (DEBUG) Log.v(TAG, "updateRowMetadata hasValidMedia " + hasValidMedia());
+
+        if (!hasValidMedia()) {
+            mControlsRow.setImageDrawable(null);
+            mControlsRow.setTotalTime(0);
+            mControlsRow.setCurrentTime(0);
+        } else {
+            mControlsRow.setImageDrawable(getMediaArt());
+            mControlsRow.setTotalTime(getMediaDuration());
+            mControlsRow.setCurrentTime(getCurrentPosition());
+        }
+
+        onRowChanged(mControlsRow);
+    }
+
+    private void updatePlaybackState() {
+        if (hasValidMedia()) {
+            mPlaybackSpeed = getCurrentSpeedId();
+            updatePlaybackState(mPlaybackSpeed);
+        }
+    }
+
+    private void updatePlaybackState(int playbackSpeed) {
+        if (mControlsRow == null) {
+            return;
+        }
+
+        final long actions = getSupportedActions();
+        if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
+            if (mSkipPreviousAction == null) {
+                mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(mContext);
+            }
+            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
+        } else {
+            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
+            mSkipPreviousAction = null;
+        }
+        if ((actions & ACTION_REWIND) != 0) {
+            if (mRewindAction == null) {
+                mRewindAction = new PlaybackControlsRow.RewindAction(mContext,
+                        mRewindSpeeds.length);
+            }
+            mPrimaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
+        } else {
+            mPrimaryActionsAdapter.clear(ACTION_REWIND);
+            mRewindAction = null;
+        }
+        if ((actions & ACTION_PLAY_PAUSE) != 0) {
+            if (mPlayPauseAction == null) {
+                mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(mContext);
+            }
+            mPrimaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
+        } else {
+            mPrimaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
+            mPlayPauseAction = null;
+        }
+        if ((actions & ACTION_FAST_FORWARD) != 0) {
+            if (mFastForwardAction == null) {
+                mFastForwardAction = new PlaybackControlsRow.FastForwardAction(mContext,
+                        mFastForwardSpeeds.length);
+            }
+            mPrimaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
+        } else {
+            mPrimaryActionsAdapter.clear(ACTION_FAST_FORWARD);
+            mFastForwardAction = null;
+        }
+        if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
+            if (mSkipNextAction == null) {
+                mSkipNextAction = new PlaybackControlsRow.SkipNextAction(mContext);
+            }
+            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
+        } else {
+            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
+            mSkipNextAction = null;
+        }
+
+        if (mFastForwardAction != null) {
+            int index = 0;
+            if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
+                index = playbackSpeed - PLAYBACK_SPEED_FAST_L0;
+                if (playbackSpeed < getMaxForwardSpeedId()) {
+                    index++;
+                }
+            }
+            if (mFastForwardAction.getIndex() != index) {
+                mFastForwardAction.setIndex(index);
+                notifyItemChanged(mPrimaryActionsAdapter, mFastForwardAction);
+            }
+        }
+        if (mRewindAction != null) {
+            int index = 0;
+            if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+                index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0;
+                if (-playbackSpeed < getMaxRewindSpeedId()) {
+                    index++;
+                }
+            }
+            if (mRewindAction.getIndex() != index) {
+                mRewindAction.setIndex(index);
+                notifyItemChanged(mPrimaryActionsAdapter, mRewindAction);
+            }
+        }
+
+        if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
+            updateProgress();
+            enableProgressUpdating(false);
+        } else {
+            enableProgressUpdating(true);
+        }
+
+        if (mFadeWhenPlaying) {
+            mFragment.setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
+        }
+
+        if (mPlayPauseAction != null) {
+            int index = playbackSpeed == PLAYBACK_SPEED_PAUSED ?
+                    PlaybackControlsRow.PlayPauseAction.PLAY :
+                    PlaybackControlsRow.PlayPauseAction.PAUSE;
+            if (mPlayPauseAction.getIndex() != index) {
+                mPlayPauseAction.setIndex(index);
+                notifyItemChanged(mPrimaryActionsAdapter, mPlayPauseAction);
+            }
+        }
+    }
+
+    private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
+        int index = adapter.indexOf(object);
+        if (index >= 0) {
+            adapter.notifyArrayItemRangeChanged(index, 1);
+        }
+    }
+
+    private static String getSpeedString(int speed) {
+        switch (speed) {
+            case PLAYBACK_SPEED_INVALID:
+                return "PLAYBACK_SPEED_INVALID";
+            case PLAYBACK_SPEED_PAUSED:
+                return "PLAYBACK_SPEED_PAUSED";
+            case PLAYBACK_SPEED_NORMAL:
+                return "PLAYBACK_SPEED_NORMAL";
+            case PLAYBACK_SPEED_FAST_L0:
+                return "PLAYBACK_SPEED_FAST_L0";
+            case PLAYBACK_SPEED_FAST_L1:
+                return "PLAYBACK_SPEED_FAST_L1";
+            case PLAYBACK_SPEED_FAST_L2:
+                return "PLAYBACK_SPEED_FAST_L2";
+            case PLAYBACK_SPEED_FAST_L3:
+                return "PLAYBACK_SPEED_FAST_L3";
+            case PLAYBACK_SPEED_FAST_L4:
+                return "PLAYBACK_SPEED_FAST_L4";
+            case -PLAYBACK_SPEED_FAST_L0:
+                return "-PLAYBACK_SPEED_FAST_L0";
+            case -PLAYBACK_SPEED_FAST_L1:
+                return "-PLAYBACK_SPEED_FAST_L1";
+            case -PLAYBACK_SPEED_FAST_L2:
+                return "-PLAYBACK_SPEED_FAST_L2";
+            case -PLAYBACK_SPEED_FAST_L3:
+                return "-PLAYBACK_SPEED_FAST_L3";
+            case -PLAYBACK_SPEED_FAST_L4:
+                return "-PLAYBACK_SPEED_FAST_L4";
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if there is a valid media item.
+     */
+    public abstract boolean hasValidMedia();
+
+    /**
+     * Returns true if media is currently playing.
+     */
+    public abstract boolean isMediaPlaying();
+
+    /**
+     * Returns the title of the media item.
+     */
+    public abstract CharSequence getMediaTitle();
+
+    /**
+     * Returns the subtitle of the media item.
+     */
+    public abstract CharSequence getMediaSubtitle();
+
+    /**
+     * Returns the duration of the media item in milliseconds.
+     */
+    public abstract int getMediaDuration();
+
+    /**
+     * Returns a bitmap of the art for the media item.
+     */
+    public abstract Drawable getMediaArt();
+
+    /**
+     * Returns a bitmask of actions supported by the media player.
+     */
+    public abstract long getSupportedActions();
+
+    /**
+     * Returns the current playback speed.  When playing normally,
+     * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
+     */
+    public abstract int getCurrentSpeedId();
+
+    /**
+     * Returns the current position of the media item in milliseconds.
+     */
+    public abstract int getCurrentPosition();
+
+    /**
+     * Start playback at the given speed.
+     * @param speed The desired playback speed.  For normal playback this will be
+     *              {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
+     *              and negative values for rewind.
+     */
+    protected abstract void startPlayback(int speed);
+
+    /**
+     * Pause playback.
+     */
+    protected abstract void pausePlayback();
+
+    /**
+     * Skip to the next track.
+     */
+    protected abstract void skipToNext();
+
+    /**
+     * Skip to the previous track.
+     */
+    protected abstract void skipToPrevious();
+
+    /**
+     * Invoked when the playback controls row has changed.  The adapter containing this row
+     * should be notified.
+     */
+    protected abstract void onRowChanged(PlaybackControlsRow row);
+
+    /**
+     * Creates the primary action adapter.  May be overridden to add additional primary
+     * actions to the adapter.
+     */
+    protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+            PresenterSelector presenterSelector) {
+        return new SparseArrayObjectAdapter(presenterSelector);
+    }
+
+    /**
+     * Must be called appropriately by a subclass when the playback state has changed.
+     */
+    protected void onStateChanged() {
+        if (DEBUG) Log.v(TAG, "onStateChanged");
+        // If a pending control button update is present, delay
+        // the update until the state settles.
+        if (!hasValidMedia()) {
+            return;
+        }
+        if (mHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE)) {
+            mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
+            if (getCurrentSpeedId() != mPlaybackSpeed) {
+                if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
+                mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
+                        UPDATE_PLAYBACK_STATE_DELAY_MS);
+            } else {
+                if (DEBUG) Log.v(TAG, "Update state matches expectation");
+                updatePlaybackState();
+            }
+        } else {
+            updatePlaybackState();
+        }
+    }
+
+    /**
+     * Must be called appropriately by a subclass when the metadata state has changed.
+     */
+    protected void onMetadataChanged() {
+        if (DEBUG) Log.v(TAG, "onMetadataChanged");
+        updateRowMetadata();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index be88c0d..53caadb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -19,6 +19,7 @@
 import android.animation.AnimatorInflater;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.view.InputEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
@@ -41,9 +42,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
 
 import java.util.ArrayList;
 
@@ -76,6 +74,16 @@
         }
     }
 
+    public interface InputEventHandler {
+        /**
+         * Called when an {@link InputEvent} is received.
+         *
+         * @return If the event should be consumed, return true. To allow the event to
+         * continue on to the next handler, return false.
+         */
+        public boolean handleInputEvent(InputEvent event);
+    }
+
     private static final String TAG = "PlaybackOverlayFragment";
     private static final boolean DEBUG = false;
     private static final int ANIMATION_MULTIPLIER = 1;
@@ -97,6 +105,7 @@
     private int mMajorFadeTranslateY, mMinorFadeTranslateY;
     private int mAnimationTranslateY;
     private OnFadeCompleteListener mFadeCompleteListener;
+    private InputEventHandler mInputEventHandler;
     private boolean mFadingEnabled = true;
     private int mFadingStatus = IDLE;
     private int mBgAlpha;
@@ -155,21 +164,14 @@
     private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
             new VerticalGridView.OnTouchInterceptListener() {
         public boolean onInterceptTouchEvent(MotionEvent event) {
-            return onInterceptInputEvent();
-        }
-    };
-
-    private final VerticalGridView.OnMotionInterceptListener mOnMotionInterceptListener =
-            new VerticalGridView.OnMotionInterceptListener() {
-        public boolean onInterceptMotionEvent(MotionEvent event) {
-            return onInterceptInputEvent();
+            return onInterceptInputEvent(event);
         }
     };
 
     private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
             new VerticalGridView.OnKeyInterceptListener() {
         public boolean onInterceptKeyEvent(KeyEvent event) {
-            return onInterceptInputEvent();
+            return onInterceptInputEvent(event);
         }
     };
 
@@ -196,6 +198,7 @@
             mResetControlsToPrimaryActionsPending = false;
             ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
                     (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
+            vh.getViewHolder().view.clearFocus();
         }
     }
 
@@ -210,16 +213,15 @@
         if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
         if (enabled != mFadingEnabled) {
             mFadingEnabled = enabled;
-            if (isResumed()) {
-                if (mFadingEnabled) {
-                    if (mFadingStatus == IDLE && !mHandler.hasMessages(START_FADE_OUT)) {
-                        startFadeTimer();
-                    }
-                } else {
-                    // Ensure fully opaque
-                    mHandler.removeMessages(START_FADE_OUT);
-                    fade(true);
+            if (mFadingEnabled) {
+                if (isResumed() && mFadingStatus == IDLE
+                        && !mHandler.hasMessages(START_FADE_OUT)) {
+                    startFadeTimer();
                 }
+            } else {
+                // Ensure fully opaque
+                mHandler.removeMessages(START_FADE_OUT);
+                fade(true);
             }
         }
     }
@@ -246,6 +248,20 @@
     }
 
     /**
+     * Sets the input event handler.
+     */
+    public final void setInputEventHandler(InputEventHandler handler) {
+        mInputEventHandler = handler;
+    }
+
+    /**
+     * Returns the input event handler.
+     */
+    public final InputEventHandler getInputEventHandler() {
+        return mInputEventHandler;
+    }
+
+    /**
      * Tickles the playback controls.  Fades in the view if it was faded out,
      * otherwise resets the fade out timer.  Tickling on input events is handled
      * by the fragment.
@@ -263,10 +279,43 @@
         }
     }
 
-    private boolean onInterceptInputEvent() {
-        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus);
-        boolean consumeEvent = (mFadingStatus == IDLE && mBgAlpha == 0);
-        tickle();
+    private static boolean isConsumableKey(KeyEvent keyEvent) {
+        if (keyEvent.isSystem()) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean onInterceptInputEvent(InputEvent event) {
+        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus +
+                " mBgAlpha " + mBgAlpha + " event " + event);
+        final boolean controlsHidden = (mFadingStatus == IDLE && mBgAlpha == 0);
+        boolean consumeEvent = controlsHidden;
+        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+        if (event instanceof KeyEvent) {
+            if (consumeEvent) {
+                consumeEvent = isConsumableKey((KeyEvent) event);
+            }
+            keyCode = ((KeyEvent) event).getKeyCode();
+        }
+        if (!consumeEvent && mInputEventHandler != null) {
+            consumeEvent = mInputEventHandler.handleInputEvent(event);
+        }
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            // If fading enabled and controls are not hidden, back will be consumed to fade
+            // them out (even if the key was consumed by the handler).
+            if (mFadingEnabled && !controlsHidden) {
+                consumeEvent = true;
+                mHandler.removeMessages(START_FADE_OUT);
+                fade(false);
+            } else if (consumeEvent) {
+                tickle();
+            }
+        } else {
+            // Any other key will show the controls
+            tickle();
+        }
         return consumeEvent;
     }
 
@@ -278,7 +327,6 @@
             fade(true);
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
-        getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
     }
 
@@ -381,6 +429,9 @@
         final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
                 final float fraction = (Float) arg0.getAnimatedValue();
                 for (View view : listener.mViews) {
                     if (getVerticalGridView().getChildPosition(view) > 0) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
new file mode 100644
index 0000000..94e4037
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -0,0 +1,742 @@
+/* This file is auto-generated from PlaybackOverlayFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.view.InputEvent;
+import android.view.animation.AccelerateInterpolator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.widget.RecyclerView;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.animation.LogAccelerateInterpolator;
+import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * A fragment for displaying playback controls and related content.
+ * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
+ * at position 0 in the adapter.
+ */
+public class PlaybackOverlaySupportFragment extends DetailsSupportFragment {
+
+    /**
+     * No background.
+     */
+    public static final int BG_NONE = 0;
+
+    /**
+     * A dark translucent background.
+     */
+    public static final int BG_DARK = 1;
+
+    /**
+     * A light translucent background.
+     */
+    public static final int BG_LIGHT = 2;
+
+    public static class OnFadeCompleteListener {
+        public void onFadeInComplete() {
+        }
+        public void onFadeOutComplete() {
+        }
+    }
+
+    public interface InputEventHandler {
+        /**
+         * Called when an {@link InputEvent} is received.
+         *
+         * @return If the event should be consumed, return true. To allow the event to
+         * continue on to the next handler, return false.
+         */
+        public boolean handleInputEvent(InputEvent event);
+    }
+
+    private static final String TAG = "PlaybackOverlaySupportFragment";
+    private static final boolean DEBUG = false;
+    private static final int ANIMATION_MULTIPLIER = 1;
+
+    private static int START_FADE_OUT = 1;
+
+    // Fading status
+    private static final int IDLE = 0;
+    private static final int IN = 1;
+    private static final int OUT = 2;
+
+    private int mAlignPosition;
+    private int mPaddingBottom;
+    private View mRootView;
+    private int mBackgroundType = BG_DARK;
+    private int mBgDarkColor;
+    private int mBgLightColor;
+    private int mShowTimeMs;
+    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
+    private int mAnimationTranslateY;
+    private OnFadeCompleteListener mFadeCompleteListener;
+    private InputEventHandler mInputEventHandler;
+    private boolean mFadingEnabled = true;
+    private int mFadingStatus = IDLE;
+    private int mBgAlpha;
+    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
+    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
+    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
+    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
+    private boolean mTranslateAnimationEnabled;
+    private boolean mResetControlsToPrimaryActionsPending;
+    private RecyclerView.ItemAnimator mItemAnimator;
+
+    private final Animator.AnimatorListener mFadeListener =
+            new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            enableVerticalGridAnimations(false);
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
+            if (mBgAlpha > 0) {
+                enableVerticalGridAnimations(true);
+                startFadeTimer();
+                if (mFadeCompleteListener != null) {
+                    mFadeCompleteListener.onFadeInComplete();
+                }
+            } else {
+                if (getVerticalGridView() != null) {
+                    // Reset focus to the controls row
+                    getVerticalGridView().setSelectedPosition(0);
+                    resetControlsToPrimaryActions(null);
+                }
+                if (mFadeCompleteListener != null) {
+                    mFadeCompleteListener.onFadeOutComplete();
+                }
+            }
+            mFadingStatus = IDLE;
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            if (message.what == START_FADE_OUT && mFadingEnabled) {
+                fade(false);
+            }
+        }
+    };
+
+    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
+            new VerticalGridView.OnTouchInterceptListener() {
+        public boolean onInterceptTouchEvent(MotionEvent event) {
+            return onInterceptInputEvent(event);
+        }
+    };
+
+    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+            new VerticalGridView.OnKeyInterceptListener() {
+        public boolean onInterceptKeyEvent(KeyEvent event) {
+            return onInterceptInputEvent(event);
+        }
+    };
+
+    private void setBgAlpha(int alpha) {
+        mBgAlpha = alpha;
+        if (mRootView != null) {
+            mRootView.getBackground().setAlpha(alpha);
+        }
+    }
+
+    private void enableVerticalGridAnimations(boolean enable) {
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setAnimateChildLayout(enable);
+        }
+    }
+
+    private void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
+        if (vh == null && getVerticalGridView() != null) {
+            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
+        }
+        if (vh == null) {
+            mResetControlsToPrimaryActionsPending = true;
+        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+            mResetControlsToPrimaryActionsPending = false;
+            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
+                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
+            vh.getViewHolder().view.clearFocus();
+        }
+    }
+
+    /**
+     * Enables or disables view fading.  If enabled,
+     * the view will be faded in when the fragment starts,
+     * and will fade out after a time period.  The timeout
+     * period is reset each time {@link #tickle} is called.
+     *
+     */
+    public void setFadingEnabled(boolean enabled) {
+        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
+        if (enabled != mFadingEnabled) {
+            mFadingEnabled = enabled;
+            if (mFadingEnabled) {
+                if (isResumed() && mFadingStatus == IDLE
+                        && !mHandler.hasMessages(START_FADE_OUT)) {
+                    startFadeTimer();
+                }
+            } else {
+                // Ensure fully opaque
+                mHandler.removeMessages(START_FADE_OUT);
+                fade(true);
+            }
+        }
+    }
+
+    /**
+     * Returns true if view fading is enabled.
+     */
+    public boolean isFadingEnabled() {
+        return mFadingEnabled;
+    }
+
+    /**
+     * Sets the listener to be called when fade in or out has completed.
+     */
+    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
+        mFadeCompleteListener = listener;
+    }
+
+    /**
+     * Returns the listener to be called when fade in or out has completed.
+     */
+    public OnFadeCompleteListener getFadeCompleteListener() {
+        return mFadeCompleteListener;
+    }
+
+    /**
+     * Sets the input event handler.
+     */
+    public final void setInputEventHandler(InputEventHandler handler) {
+        mInputEventHandler = handler;
+    }
+
+    /**
+     * Returns the input event handler.
+     */
+    public final InputEventHandler getInputEventHandler() {
+        return mInputEventHandler;
+    }
+
+    /**
+     * Tickles the playback controls.  Fades in the view if it was faded out,
+     * otherwise resets the fade out timer.  Tickling on input events is handled
+     * by the fragment.
+     */
+    public void tickle() {
+        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
+        if (!mFadingEnabled || !isResumed()) {
+            return;
+        }
+        if (mHandler.hasMessages(START_FADE_OUT)) {
+            // Restart the timer
+            startFadeTimer();
+        } else {
+            fade(true);
+        }
+    }
+
+    private static boolean isConsumableKey(KeyEvent keyEvent) {
+        if (keyEvent.isSystem()) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean onInterceptInputEvent(InputEvent event) {
+        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus +
+                " mBgAlpha " + mBgAlpha + " event " + event);
+        final boolean controlsHidden = (mFadingStatus == IDLE && mBgAlpha == 0);
+        boolean consumeEvent = controlsHidden;
+        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+
+        if (event instanceof KeyEvent) {
+            if (consumeEvent) {
+                consumeEvent = isConsumableKey((KeyEvent) event);
+            }
+            keyCode = ((KeyEvent) event).getKeyCode();
+        }
+        if (!consumeEvent && mInputEventHandler != null) {
+            consumeEvent = mInputEventHandler.handleInputEvent(event);
+        }
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            // If fading enabled and controls are not hidden, back will be consumed to fade
+            // them out (even if the key was consumed by the handler).
+            if (mFadingEnabled && !controlsHidden) {
+                consumeEvent = true;
+                mHandler.removeMessages(START_FADE_OUT);
+                fade(false);
+            } else if (consumeEvent) {
+                tickle();
+            }
+        } else {
+            // Any other key will show the controls
+            tickle();
+        }
+        return consumeEvent;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mFadingEnabled) {
+            setBgAlpha(0);
+            fade(true);
+        }
+        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
+        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+    }
+
+    private void startFadeTimer() {
+        if (mHandler != null) {
+            mHandler.removeMessages(START_FADE_OUT);
+            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
+        }
+    }
+
+    private static ValueAnimator loadAnimator(Context context, int resId) {
+        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
+        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
+        return animator;
+    }
+
+    private void loadBgAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                setBgAlpha((Integer) arg0.getAnimatedValue());
+            }
+        };
+
+        mBgFadeInAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_in);
+        mBgFadeInAnimator.addUpdateListener(listener);
+        mBgFadeInAnimator.addListener(mFadeListener);
+
+        mBgFadeOutAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_out);
+        mBgFadeOutAnimator.addUpdateListener(listener);
+        mBgFadeOutAnimator.addListener(mFadeListener);
+    }
+
+    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
+    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
+
+    private View getControlRowView() {
+        if (getVerticalGridView() == null) {
+            return null;
+        }
+        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
+        if (vh == null) {
+            return null;
+        }
+        return vh.itemView;
+    }
+
+    private void loadControlRowAnimator() {
+        final AnimatorListener listener = new AnimatorListener() {
+            @Override
+            void getViews(ArrayList<View> views) {
+                View view = getControlRowView();
+                if (view != null) {
+                    views.add(view);
+                }
+            }
+        };
+        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                View view = getControlRowView();
+                if (view != null) {
+                    final float fraction = (Float) arg0.getAnimatedValue();
+                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
+                    view.setAlpha(fraction);
+                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                }
+            }
+        };
+
+        mControlRowFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_in);
+        mControlRowFadeInAnimator.addUpdateListener(updateListener);
+        mControlRowFadeInAnimator.addListener(listener);
+        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mControlRowFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_out);
+        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
+        mControlRowFadeOutAnimator.addListener(listener);
+        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
+    }
+
+    private void loadOtherRowAnimator() {
+        final AnimatorListener listener = new AnimatorListener() {
+            @Override
+            void getViews(ArrayList<View> views) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                final int count = getVerticalGridView().getChildCount();
+                for (int i = 0; i < count; i++) {
+                    View view = getVerticalGridView().getChildAt(i);
+                    if (view != null) {
+                        views.add(view);
+                    }
+                }
+            }
+        };
+        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                final float fraction = (Float) arg0.getAnimatedValue();
+                for (View view : listener.mViews) {
+                    if (getVerticalGridView().getChildPosition(view) > 0) {
+                        view.setAlpha(fraction);
+                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                    }
+                }
+            }
+        };
+
+        mOtherRowFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_in);
+        mOtherRowFadeInAnimator.addListener(listener);
+        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
+        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mOtherRowFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_out);
+        mOtherRowFadeOutAnimator.addListener(listener);
+        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
+        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
+    }
+
+    private void loadDescriptionAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
+                        getVerticalGridView().findViewHolderForPosition(0);
+                if (adapterVh != null && adapterVh.getViewHolder()
+                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
+                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
+                            adapterVh.getViewHolder()).mDescriptionViewHolder;
+                    if (vh != null) {
+                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
+                    }
+                }
+            }
+        };
+
+        mDescriptionFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_description_fade_in);
+        mDescriptionFadeInAnimator.addUpdateListener(listener);
+        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mDescriptionFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_description_fade_out);
+        mDescriptionFadeOutAnimator.addUpdateListener(listener);
+    }
+
+    private void fade(boolean fadeIn) {
+        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
+        if (getView() == null) {
+            return;
+        }
+        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
+            if (DEBUG) Log.v(TAG, "requested fade in progress");
+            return;
+        }
+        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
+            if (DEBUG) Log.v(TAG, "fade is no-op");
+            return;
+        }
+
+        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0 ?
+                mMajorFadeTranslateY : mMinorFadeTranslateY;
+
+        if (mFadingStatus == IDLE) {
+            if (fadeIn) {
+                mBgFadeInAnimator.start();
+                mControlRowFadeInAnimator.start();
+                mOtherRowFadeInAnimator.start();
+                mDescriptionFadeInAnimator.start();
+            } else {
+                mBgFadeOutAnimator.start();
+                mControlRowFadeOutAnimator.start();
+                mOtherRowFadeOutAnimator.start();
+                mDescriptionFadeOutAnimator.start();
+            }
+        } else {
+            if (fadeIn) {
+                mBgFadeOutAnimator.reverse();
+                mControlRowFadeOutAnimator.reverse();
+                mOtherRowFadeOutAnimator.reverse();
+                mDescriptionFadeOutAnimator.reverse();
+            } else {
+                mBgFadeInAnimator.reverse();
+                mControlRowFadeInAnimator.reverse();
+                mOtherRowFadeInAnimator.reverse();
+                mDescriptionFadeInAnimator.reverse();
+            }
+        }
+
+        // If fading in while control row is focused, set initial translationY so
+        // views slide in from below.
+        if (fadeIn && mFadingStatus == IDLE) {
+            final int count = getVerticalGridView().getChildCount();
+            for (int i = 0; i < count; i++) {
+                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
+            }
+        }
+
+        mFadingStatus = fadeIn ? IN : OUT;
+    }
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    @Override
+    public void setAdapter(ObjectAdapter adapter) {
+        if (getAdapter() != null) {
+            getAdapter().unregisterObserver(mObserver);
+        }
+        super.setAdapter(adapter);
+        if (adapter != null) {
+            adapter.registerObserver(mObserver);
+        }
+    }
+
+    @Override
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        if (listview == null) {
+            return;
+        }
+        // Padding affects alignment when last row is focused
+        // (last is first when there's only one row).
+        setBottomPadding(listview, mPaddingBottom);
+
+        // Item alignment affects focused row that isn't the last.
+        listview.setItemAlignmentOffset(mAlignPosition);
+        listview.setItemAlignmentOffsetPercent(100);
+
+        // Push rows to the bottom.
+        listview.setWindowAlignmentOffset(0);
+        listview.setWindowAlignmentOffsetPercent(100);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+    }
+
+    private static void setBottomPadding(View view, int padding) {
+        view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+                view.getPaddingRight(), padding);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAlignPosition =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
+        mPaddingBottom =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
+        mBgDarkColor =
+                getResources().getColor(R.color.lb_playback_controls_background_dark);
+        mBgLightColor =
+                getResources().getColor(R.color.lb_playback_controls_background_light);
+        mShowTimeMs =
+                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
+        mMajorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
+        mMinorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
+
+        loadBgAnimator();
+        loadControlRowAnimator();
+        loadOtherRowAnimator();
+        loadDescriptionAnimator();
+    }
+
+    /**
+     * Sets the background type.
+     *
+     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
+     */
+    public void setBackgroundType(int type) {
+        switch (type) {
+        case BG_LIGHT:
+        case BG_DARK:
+        case BG_NONE:
+            if (type != mBackgroundType) {
+                mBackgroundType = type;
+                updateBackground();
+            }
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid background type");
+        }
+    }
+
+    /**
+     * Returns the background type.
+     */
+    public int getBackgroundType() {
+        return mBackgroundType;
+    }
+
+    private void updateBackground() {
+        if (mRootView != null) {
+            int color = mBgDarkColor;
+            switch (mBackgroundType) {
+                case BG_DARK: break;
+                case BG_LIGHT: color = mBgLightColor; break;
+                case BG_NONE: color = Color.TRANSPARENT; break;
+            }
+            mRootView.setBackground(new ColorDrawable(color));
+        }
+    }
+
+    private void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
+        // Add extra space between rows 0 and 1
+        if (vh == null && getVerticalGridView() != null) {
+            vh = (ItemBridgeAdapter.ViewHolder)
+                    getVerticalGridView().findViewHolderForPosition(0);
+        }
+        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
+            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
+                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
+                    adapterSize > 1);
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
+            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
+                if (DEBUG) Log.v(TAG, "setting alpha to 0");
+                vh.getViewHolder().view.setAlpha(0);
+            }
+            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
+                resetControlsToPrimaryActions(vh);
+            }
+        }
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
+            // Reset animation state
+            vh.getViewHolder().view.setAlpha(1f);
+            vh.getViewHolder().view.setTranslationY(0);
+            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
+                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
+                        vh.getViewHolder()).mDescriptionViewHolder;
+                if (descriptionVh != null) {
+                    descriptionVh.view.setAlpha(1f);
+                }
+            }
+        }
+        @Override
+        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+            if (vh.getPosition() == 0) {
+                updateControlsBottomSpace(vh);
+            }
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mRootView = super.onCreateView(inflater, container, savedInstanceState);
+        mBgAlpha = 255;
+        updateBackground();
+        getRowsSupportFragment().setExternalAdapterListener(mAdapterListener);
+        return mRootView;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mRootView = null;
+        super.onDestroyView();
+    }
+
+    private final DataObserver mObserver = new DataObserver() {
+        public void onChanged() {
+            updateControlsBottomSpace(null);
+        }
+    };
+
+    static abstract class AnimatorListener implements Animator.AnimatorListener {
+        ArrayList<View> mViews = new ArrayList<View>();
+        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
+
+        public void onAnimationCancel(Animator animation) {
+        }
+        public void onAnimationRepeat(Animator animation) {
+        }
+        public void onAnimationStart(Animator animation) {
+            getViews(mViews);
+            for (View view : mViews) {
+                mLayerType.add(view.getLayerType());
+                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            }
+        }
+        public void onAnimationEnd(Animator animation) {
+            for (int i = 0; i < mViews.size(); i++) {
+                mViews.get(i).setLayerType(mLayerType.get(i), null);
+            }
+            mLayerType.clear();
+            mViews.clear();
+        }
+        abstract void getViews(ArrayList<View> views);
+    };
+}
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 4e79ed3..ff61927 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -23,6 +23,7 @@
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
 import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
@@ -32,6 +33,7 @@
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -117,7 +119,11 @@
     private boolean mExpand = true;
     private boolean mViewsCreated;
     private float mRowScaleFactor;
+    private int mAlignedTop;
     private boolean mRowScaleEnabled;
+    private ScaleFrameLayout mScaleFrameLayout;
+    private boolean mInTransition;
+    private boolean mAfterEntranceTransition = true;
 
     private OnItemSelectedListener mOnItemSelectedListener;
     private OnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -135,6 +141,11 @@
 
     private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
 
+    @Override
+    protected VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view.findViewById(R.id.container_list);
+    }
+
     /**
      * Sets an item clicked listener on the fragment.
      * OnItemClickedListener will override {@link View.OnClickListener} that
@@ -186,7 +197,7 @@
         mExpand = expand;
         VerticalGridView listView = getVerticalGridView();
         if (listView != null) {
-            updateRowScaling(!expand);
+            updateRowScaling();
             final int count = listView.getChildCount();
             if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
             for (int i = 0; i < count; i++) {
@@ -249,7 +260,7 @@
     }
 
     @Override
-    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
         VerticalGridView listView = getVerticalGridView();
         if (listView == null) {
             return;
@@ -271,7 +282,7 @@
     }
 
     @Override
-    protected int getLayoutResourceId() {
+    int getLayoutResourceId() {
         return R.layout.lb_rows_fragment;
     }
 
@@ -285,6 +296,14 @@
     }
 
     @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
+        return view;
+    }
+
+    @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         if (DEBUG) Log.v(TAG, "onViewCreated");
         super.onViewCreated(view, savedInstanceState);
@@ -298,6 +317,12 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mViewsCreated = false;
+        super.onDestroyView();
+    }
+
+    @Override
     void setItemAlignment() {
         super.setItemAlignment();
         if (getVerticalGridView() != null) {
@@ -309,6 +334,23 @@
         mExternalAdapterListener = listener;
     }
 
+    /**
+     * Get the view that will change scale.
+     */
+    View getScaleView() {
+        return getVerticalGridView();
+    }
+
+    /**
+     * Set pivots to scale rows fragment.
+     */
+    void setScalePivots(float pivotX, float pivotY) {
+        // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
+        // where we actually change scale.
+        mScaleFrameLayout.setPivotX(pivotX);
+        mScaleFrameLayout.setPivotY(pivotY);
+    }
+
     private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
         ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
     }
@@ -343,7 +385,8 @@
         @Override
         public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
             VerticalGridView listView = getVerticalGridView();
-            if (listView != null && ((RowPresenter) vh.getPresenter()).canDrawOutOfBounds()) {
+            if (listView != null) {
+                // set clip children false for slide animation
                 listView.setClipChildren(false);
             }
             setupSharedViewPool(vh);
@@ -368,6 +411,9 @@
             setRowViewExpanded(vh, mExpand);
             setOnItemSelectedListener(vh, mOnItemSelectedListener);
             setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onAttachedToWindow(vh);
             }
@@ -422,7 +468,7 @@
     }
 
     @Override
-    protected void updateAdapter() {
+    void updateAdapter() {
         super.updateAdapter();
         mSelectedViewHolder = null;
         mViewsCreated = false;
@@ -436,6 +482,7 @@
     @Override
     void onTransitionStart() {
         super.onTransitionStart();
+        mInTransition = true;
         freezeRows(true);
     }
 
@@ -485,19 +532,43 @@
         new ExpandPreLayout(callback).execute();
     }
 
-    private void updateRowScaling(boolean scale) {
-        VerticalGridView view = getVerticalGridView();
-        view.setClipChildren(!mRowScaleEnabled && scale);
-        view.setPrimaryOverReach((mRowScaleEnabled && scale) ? 1f / mRowScaleFactor : 1f);
+    private boolean needsScale() {
+        return mRowScaleEnabled && !mExpand;
+    }
 
-        final float scaleFactor = (mRowScaleEnabled && scale) ? mRowScaleFactor : 1f;
-        view.setScaleX(scaleFactor);
-        view.setScaleY(scaleFactor);
+    private void updateRowScaling() {
+        final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        getScaleView().setScaleY(scaleFactor);
+        getScaleView().setScaleX(scaleFactor);
+        updateWindowAlignOffset();
+    }
+
+    private void updateWindowAlignOffset() {
+        int alignOffset = mAlignedTop;
+        if (needsScale()) {
+            alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
+        }
+        getVerticalGridView().setWindowAlignmentOffset(alignOffset);
+    }
+
+    @Override
+    void setWindowAlignmentFromTop(int alignedTop) {
+        mAlignedTop = alignedTop;
+        final VerticalGridView gridView = getVerticalGridView();
+        if (gridView != null) {
+            updateWindowAlignOffset();
+            // align to a fixed position from top
+            gridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
     }
 
     @Override
     void onTransitionEnd() {
         super.onTransitionEnd();
+        mInTransition = false;
         freezeRows(false);
     }
 
@@ -514,4 +585,23 @@
             }
         }
     }
+
+    /**
+     * For rows that willing to participate entrance transition,  this function
+     * hide views if afterTransition is true,  show views if afterTransition is false.
+     */
+    void setEntranceTransitionState(boolean afterTransition) {
+        mAfterEntranceTransition = afterTransition;
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView != null) {
+            final int count = verticalView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
+            }
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
new file mode 100644
index 0000000..4e95878
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -0,0 +1,609 @@
+/* This file is auto-generated from RowsFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import java.util.ArrayList;
+
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An ordered set of rows of leanback widgets.
+ */
+public class RowsSupportFragment extends BaseRowSupportFragment {
+
+    /**
+     * Internal helper class that manages row select animation and apply a default
+     * dim to each row.
+     */
+    final class RowViewHolderExtra implements TimeListener {
+        final RowPresenter mRowPresenter;
+        final Presenter.ViewHolder mRowViewHolder;
+
+        final TimeAnimator mSelectAnimator = new TimeAnimator();
+
+        int mSelectAnimatorDurationInUse;
+        Interpolator mSelectAnimatorInterpolatorInUse;
+        float mSelectLevelAnimStart;
+        float mSelectLevelAnimDelta;
+
+        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
+            mRowPresenter = (RowPresenter) ibvh.getPresenter();
+            mRowViewHolder = ibvh.getViewHolder();
+            mSelectAnimator.setTimeListener(this);
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            if (mSelectAnimator.isRunning()) {
+                updateSelect(totalTime, deltaTime);
+            }
+        }
+
+        void updateSelect(long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mSelectAnimatorDurationInUse) {
+                fraction = 1;
+                mSelectAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
+            }
+            if (mSelectAnimatorInterpolatorInUse != null) {
+                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
+            }
+            float level =  mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            mRowPresenter.setSelectLevel(mRowViewHolder, level);
+        }
+
+        void animateSelect(boolean select, boolean immediate) {
+            endSelectAnimation();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                mRowPresenter.setSelectLevel(mRowViewHolder, end);
+            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
+                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
+                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
+                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
+                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
+                mSelectAnimator.start();
+            }
+        }
+
+        void endAnimations() {
+            endSelectAnimation();
+        }
+
+        void endSelectAnimation() {
+            mSelectAnimator.end();
+        }
+
+    }
+
+    private static final String TAG = "RowsSupportFragment";
+    private static final boolean DEBUG = false;
+
+    private ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
+    private boolean mExpand = true;
+    private boolean mViewsCreated;
+    private float mRowScaleFactor;
+    private int mAlignedTop;
+    private boolean mRowScaleEnabled;
+    private ScaleFrameLayout mScaleFrameLayout;
+    private boolean mInTransition;
+    private boolean mAfterEntranceTransition = true;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+
+    // Select animation and interpolator are not intended to be
+    // exposed at this moment. They might be synced with vertical scroll
+    // animation later.
+    int mSelectAnimatorDuration;
+    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
+
+    private RecyclerView.RecycledViewPool mRecycledViewPool;
+    private ArrayList<Presenter> mPresenterMapper;
+
+    private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
+
+    @Override
+    protected VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view.findViewById(R.id.container_list);
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Set the visibility of titles/hovercard of browse rows.
+     */
+    public void setExpand(boolean expand) {
+        mExpand = expand;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            updateRowScaling();
+            final int count = listView.getChildCount();
+            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+                setRowViewExpanded(vh, mExpand);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                setOnItemSelectedListener(vh, mOnItemSelectedListener);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+            }
+        }
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    /**
+     * Enables scaling of rows.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableRowScaling(boolean enable) {
+        mRowScaleEnabled = enable;
+    }
+
+    @Override
+    void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        ItemBridgeAdapter.ViewHolder vh = (view == null) ? null :
+            (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+
+        if (mSelectedViewHolder != vh) {
+            if (DEBUG) Log.v(TAG, "new row selected position " + position + " view " + view);
+
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, false, false);
+            }
+            mSelectedViewHolder = vh;
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, true, false);
+            }
+        }
+    }
+
+    @Override
+    int getLayoutResourceId() {
+        return R.layout.lb_rows_fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSelectAnimatorDuration = getResources().getInteger(
+                R.integer.lb_browse_rows_anim_duration);
+        mRowScaleFactor = getResources().getFraction(
+                R.fraction.lb_browse_rows_scale, 1, 1);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = super.onCreateView(inflater, container, savedInstanceState);
+        mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
+        return view;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onViewCreated");
+        super.onViewCreated(view, savedInstanceState);
+        // Align the top edge of child with id row_content.
+        // Need set this for directly using RowsSupportFragment.
+        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
+        getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
+
+        mRecycledViewPool = null;
+        mPresenterMapper = null;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mViewsCreated = false;
+        super.onDestroyView();
+    }
+
+    @Override
+    void setItemAlignment() {
+        super.setItemAlignment();
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
+        }
+    }
+
+    void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
+        mExternalAdapterListener = listener;
+    }
+
+    /**
+     * Get the view that will change scale.
+     */
+    View getScaleView() {
+        return getVerticalGridView();
+    }
+
+    /**
+     * Set pivots to scale rows fragment.
+     */
+    void setScalePivots(float pivotX, float pivotY) {
+        // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
+        // where we actually change scale.
+        mScaleFrameLayout.setPivotX(pivotX);
+        mScaleFrameLayout.setPivotY(pivotY);
+    }
+
+    private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
+        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
+    }
+
+    private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
+            boolean immediate) {
+        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+        extra.animateSelect(selected, immediate);
+        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
+    }
+
+    private static void setOnItemSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+            OnItemSelectedListener listener) {
+        ((RowPresenter) vh.getPresenter()).setOnItemSelectedListener(listener);
+    }
+
+    private 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).setOnItemClickedListener(mOnItemClickedListener);
+            ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAddPresenter(presenter, type);
+            }
+        }
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+            VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                // set clip children false for slide animation
+                listView.setClipChildren(false);
+            }
+            setupSharedViewPool(vh);
+            mViewsCreated = true;
+            vh.setExtraObject(new RowViewHolderExtra(vh));
+            // selected state is initialized to false, then driven by grid view onChildSelected
+            // events.  When there is rebind, grid view fires onChildSelected event properly.
+            // So we don't need do anything special later in onBind or onAttachedToWindow.
+            setRowViewSelected(vh, false, true);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onCreate(vh);
+            }
+        }
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachToWindow");
+            // All views share the same mExpand value.  When we attach a view to grid view,
+            // we should make sure it pick up the latest mExpand value we set early on other
+            // attached views.  For no-structure-change update,  the view is rebound to new data,
+            // but again it should use the unchanged mExpand value,  so we don't need do any
+            // thing in onBind.
+            setRowViewExpanded(vh, mExpand);
+            setOnItemSelectedListener(vh, mOnItemSelectedListener);
+            setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAttachedToWindow(vh);
+            }
+        }
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (mSelectedViewHolder == vh) {
+                setRowViewSelected(mSelectedViewHolder, false, true);
+                mSelectedViewHolder = null;
+            }
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onDetachedFromWindow(vh);
+            }
+        }
+        @Override
+        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onBind(vh);
+            }
+        }
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
+            RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+            extra.endAnimations();
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onUnbind(vh);
+            }
+        }
+    };
+
+    private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
+        RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
+        RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
+
+        if (rowVh instanceof ListRowPresenter.ViewHolder) {
+            HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
+            // Recycled view pool is shared between all list rows
+            if (mRecycledViewPool == null) {
+                mRecycledViewPool = view.getRecycledViewPool();
+            } else {
+                view.setRecycledViewPool(mRecycledViewPool);
+            }
+
+            ItemBridgeAdapter bridgeAdapter =
+                    ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
+            if (mPresenterMapper == null) {
+                mPresenterMapper = bridgeAdapter.getPresenterMapper();
+            } else {
+                bridgeAdapter.setPresenterMapper(mPresenterMapper);
+            }
+        }
+    }
+
+    @Override
+    void updateAdapter() {
+        super.updateAdapter();
+        mSelectedViewHolder = null;
+        mViewsCreated = false;
+
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mBridgeAdapterListener);
+        }
+    }
+
+    @Override
+    void onTransitionStart() {
+        super.onTransitionStart();
+        mInTransition = true;
+        freezeRows(true);
+    }
+
+    class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+
+        final View mVerticalView;
+        final Runnable mCallback;
+        int mState;
+
+        final static int STATE_INIT = 0;
+        final static int STATE_FIRST_DRAW = 1;
+        final static int STATE_SECOND_DRAW = 2;
+
+        ExpandPreLayout(Runnable callback) {
+            mVerticalView = getVerticalGridView();
+            mCallback = callback;
+        }
+
+        void execute() {
+            mVerticalView.getViewTreeObserver().addOnPreDrawListener(this);
+            setExpand(false);
+            mState = STATE_INIT;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (mState == STATE_INIT) {
+                setExpand(true);
+                mState = STATE_FIRST_DRAW;
+            } else if (mState == STATE_FIRST_DRAW) {
+                mCallback.run();
+                mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mState = STATE_SECOND_DRAW;
+            }
+            return false;
+        }
+    }
+
+    void onExpandTransitionStart(boolean expand, final Runnable callback) {
+        onTransitionStart();
+        if (expand) {
+            callback.run();
+            return;
+        }
+        // Run a "pre" layout when we go non-expand, in order to get the initial
+        // positions of added rows.
+        new ExpandPreLayout(callback).execute();
+    }
+
+    private boolean needsScale() {
+        return mRowScaleEnabled && !mExpand;
+    }
+
+    private void updateRowScaling() {
+        final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        getScaleView().setScaleY(scaleFactor);
+        getScaleView().setScaleX(scaleFactor);
+        updateWindowAlignOffset();
+    }
+
+    private void updateWindowAlignOffset() {
+        int alignOffset = mAlignedTop;
+        if (needsScale()) {
+            alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
+        }
+        getVerticalGridView().setWindowAlignmentOffset(alignOffset);
+    }
+
+    @Override
+    void setWindowAlignmentFromTop(int alignedTop) {
+        mAlignedTop = alignedTop;
+        final VerticalGridView gridView = getVerticalGridView();
+        if (gridView != null) {
+            updateWindowAlignOffset();
+            // align to a fixed position from top
+            gridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+
+    @Override
+    void onTransitionEnd() {
+        super.onTransitionEnd();
+        mInTransition = false;
+        freezeRows(false);
+    }
+
+    private void freezeRows(boolean freeze) {
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView != null) {
+            final int count = verticalView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                rowPresenter.freeze(vh, freeze);
+            }
+        }
+    }
+
+    /**
+     * For rows that willing to participate entrance transition,  this function
+     * hide views if afterTransition is true,  show views if afterTransition is false.
+     */
+    void setEntranceTransitionState(boolean afterTransition) {
+        mAfterEntranceTransition = afterTransition;
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView != null) {
+            final int count = verticalView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
index f2d83b4..2299b5b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -64,12 +64,16 @@
     private static final String TAG = SearchFragment.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
     private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
     private static final String ARG_QUERY =  ARG_PREFIX + ".query";
     private static final String ARG_TITLE = ARG_PREFIX  + ".title";
 
     private static final long SPEECH_RECOGNITION_DELAY_MS = 300;
 
+    private static final int RESULTS_CHANGED = 0x1;
+    private static final int QUERY_COMPLETE = 0x2;
+
     /**
      * Search API to be provided by the application.
      */
@@ -124,26 +128,35 @@
     private final Runnable mResultsChangedCallback = new Runnable() {
         @Override
         public void run() {
-            if (DEBUG) Log.v(TAG, "adapter size " + mResultAdapter.size());
+            if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
             if (mRowsFragment != null
                     && mRowsFragment.getAdapter() != mResultAdapter) {
                 if (!(mRowsFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
                     mRowsFragment.setAdapter(mResultAdapter);
+                    mRowsFragment.setSelectedPosition(0);
                 }
             }
             mStatus |= RESULTS_CHANGED;
             if ((mStatus & QUERY_COMPLETE) != 0) {
-                focusOnResults();
+                updateFocus();
             }
             updateSearchBarNextFocusId();
         }
     };
 
+    /**
+     * Runs when a new provider is set AND when the fragment view is created.
+     */
     private final Runnable mSetSearchResultProvider = new Runnable() {
         @Override
         public void run() {
+            if (mRowsFragment == null) {
+                // We'll retry once we have a rows fragment
+                return;
+            }
             // Retrieve the result adapter
             ObjectAdapter adapter = mProvider.getResultsAdapter();
+            if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
             if (adapter != mResultAdapter) {
                 boolean firstTime = mResultAdapter == null;
                 releaseAdapter();
@@ -151,16 +164,34 @@
                 if (mResultAdapter != null) {
                     mResultAdapter.registerObserver(mAdapterObserver);
                 }
-                if (null != mRowsFragment) {
-                    // delay the first time to avoid setting a empty result adapter
-                    // until we got first onChange() from the provider
-                    if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
-                        mRowsFragment.setAdapter(mResultAdapter);
-                    }
-                    executePendingQuery();
+                if (DEBUG) Log.v(TAG, "mResultAdapter " + mResultAdapter + " size " +
+                        (mResultAdapter == null ? 0 : mResultAdapter.size()));
+                // delay the first time to avoid setting a empty result adapter
+                // until we got first onChange() from the provider
+                if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
+                    mRowsFragment.setAdapter(mResultAdapter);
                 }
-                updateSearchBarNextFocusId();
+                executePendingQuery();
             }
+            updateSearchBarNextFocusId();
+
+            if (DEBUG) Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition +
+                    " mResultAdapter " + mResultAdapter +
+                    " adapter " + mRowsFragment.getAdapter());
+            if (mAutoStartRecognition) {
+                mHandler.removeCallbacks(mStartRecognitionRunnable);
+                mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
+            } else {
+                updateFocus();
+            }
+        }
+    };
+
+    private final Runnable mStartRecognitionRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mAutoStartRecognition = false;
+            mSearchBar.startRecognition();
         }
     };
 
@@ -178,13 +209,12 @@
 
     private String mTitle;
     private Drawable mBadgeDrawable;
+    private ExternalQuery mExternalQuery;
 
     private SpeechRecognizer mSpeechRecognizer;
 
-    private final int RESULTS_CHANGED = 0x1;
-    private final int QUERY_COMPLETE = 0x2;
-
     private int mStatus;
+    private boolean mAutoStartRecognition = true;
 
     /**
      * @param args Bundle to use for the arguments, if null a new Bundle will be created.
@@ -220,6 +250,9 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = savedInstanceState == null;
+        }
         super.onCreate(savedInstanceState);
     }
 
@@ -245,10 +278,7 @@
             @Override
             public void onSearchQuerySubmit(String query) {
                 if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
-                queryComplete();
-                if (null != mProvider) {
-                    mProvider.onQueryTextSubmit(query);
-                }
+                submitQuery(query);
             }
 
             @Override
@@ -258,6 +288,7 @@
             }
         });
         mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        applyExternalQuery();
 
         readArguments(getArguments());
         if (null != mBadgeDrawable) {
@@ -311,18 +342,16 @@
         if (null != mProvider) {
             onSetSearchResultProvider();
         }
-        if (savedInstanceState == null) {
-            // auto start recognition if this is the first time create fragment
-            mHandler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    mSearchBar.startRecognition();
-                }
-            }, SPEECH_RECOGNITION_DELAY_MS);
-        }
         return root;
     }
 
+    private void resultsAvailable() {
+        if ((mStatus & QUERY_COMPLETE) != 0) {
+            focusOnResults();
+        }
+        updateSearchBarNextFocusId();
+    }
+
     @Override
     public void onStart() {
         super.onStart();
@@ -344,6 +373,8 @@
             mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
             mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
         }
+        // Ensure search bar state consistency when using external recognizer
+        mSearchBar.stopRecognition();
     }
 
     @Override
@@ -508,10 +539,15 @@
      * @param submit Whether to submit the query.
      */
     public void setSearchQuery(String query, boolean submit) {
-        // setSearchQuery will call onQueryTextChange
-        mSearchBar.setSearchQuery(query);
-        if (submit) {
-            mProvider.onQueryTextSubmit(query);
+        if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
+        if (query == null) {
+            return;
+        }
+        mExternalQuery = new ExternalQuery(query, submit);
+        applyExternalQuery();
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = false;
+            mHandler.removeCallbacks(mStartRecognitionRunnable);
         }
     }
 
@@ -553,16 +589,26 @@
         if (mSearchBar != null && mSearchBar.getHint() != null) {
             recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
         }
+        recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
         return recognizerIntent;
     }
 
     private void retrieveResults(String searchQuery) {
-        if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
-        mProvider.onQueryTextChange(searchQuery);
-        mStatus &= ~QUERY_COMPLETE;
+        if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
+        if (mProvider.onQueryTextChange(searchQuery)) {
+            mStatus &= ~QUERY_COMPLETE;
+        }
+    }
+
+    private void submitQuery(String query) {
+        queryComplete();
+        if (null != mProvider) {
+            mProvider.onQueryTextSubmit(query);
+        }
     }
 
     private void queryComplete() {
+        if (DEBUG) Log.v(TAG, "queryComplete");
         mStatus |= QUERY_COMPLETE;
         focusOnResults();
     }
@@ -577,13 +623,21 @@
         mSearchBar.setNextFocusDownId(viewId);
     }
 
+    private void updateFocus() {
+        if (mResultAdapter != null && mResultAdapter.size() > 0 &&
+                mRowsFragment != null && mRowsFragment.getAdapter() == mResultAdapter) {
+            focusOnResults();
+        } else {
+            mSearchBar.requestFocus();
+        }
+    }
+
     private void focusOnResults() {
         if (mRowsFragment == null ||
                 mRowsFragment.getVerticalGridView() == null ||
                 mResultAdapter.size() == 0) {
             return;
         }
-        mRowsFragment.setSelectedPosition(0);
         if (mRowsFragment.getVerticalGridView().requestFocus()) {
             mStatus &= ~RESULTS_CHANGED;
         }
@@ -609,6 +663,17 @@
         }
     }
 
+    private void applyExternalQuery() {
+        if (mExternalQuery == null || mSearchBar == null) {
+            return;
+        }
+        mSearchBar.setSearchQuery(mExternalQuery.mQuery);
+        if (mExternalQuery.mSubmit) {
+            submitQuery(mExternalQuery.mQuery);
+        }
+        mExternalQuery = null;
+    }
+
     private void readArguments(Bundle args) {
         if (null == args) {
             return;
@@ -625,4 +690,14 @@
     private void setSearchQuery(String query) {
         mSearchBar.setSearchQuery(query);
     }
+
+    static class ExternalQuery {
+        String mQuery;
+        boolean mSubmit;
+
+        ExternalQuery(String query, boolean submit) {
+            mQuery = query;
+            mSubmit = submit;
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
new file mode 100644
index 0000000..b3c280f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -0,0 +1,705 @@
+/* This file is auto-generated from SearchFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.Fragment;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.speech.SpeechRecognizer;
+import android.speech.RecognizerIntent;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchBar;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
+import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.support.v17.leanback.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment to handle searches. An application will supply an implementation
+ * of the {@link SearchResultProvider} interface to handle the search and return
+ * an {@link ObjectAdapter} containing the results. The results are rendered
+ * into a {@link RowsSupportFragment}, in the same way that they are in a {@link
+ * BrowseSupportFragment}.
+ *
+ * <p>If you do not supply a callback via
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
+ * recognizer will be used for which your application will need to request
+ * android.permission.RECORD_AUDIO.
+ * </p>
+ * <p>
+ * Speech recognition is automatically started when fragment is created, but
+ * not when fragment is restored from an instance state.  Activity may manually
+ * call {@link #startRecognition()}, typically in onNewIntent().
+ * </p>
+ */
+public class SearchSupportFragment extends Fragment {
+    private static final String TAG = SearchSupportFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
+    private static final String ARG_PREFIX = SearchSupportFragment.class.getCanonicalName();
+    private static final String ARG_QUERY =  ARG_PREFIX + ".query";
+    private static final String ARG_TITLE = ARG_PREFIX  + ".title";
+
+    private static final long SPEECH_RECOGNITION_DELAY_MS = 300;
+
+    private static final int RESULTS_CHANGED = 0x1;
+    private static final int QUERY_COMPLETE = 0x2;
+
+    /**
+     * Search API to be provided by the application.
+     */
+    public static interface SearchResultProvider {
+        /**
+         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
+         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
+         *
+         * <p>As results are retrieved, the application should use the data set notification methods
+         * on the ObjectAdapter to instruct the SearchSupportFragment to update the results.</p>
+         *
+         * @return ObjectAdapter The result object adapter.
+         */
+        public ObjectAdapter getResultsAdapter();
+
+        /**
+         * <p>Method invoked when the search query is updated.</p>
+         *
+         * <p>This is called as soon as the query changes; it is up to the application to add a
+         * delay before actually executing the queries if needed.
+         *
+         * <p>This method might not always be called before onQueryTextSubmit gets called, in
+         * particular for voice input.
+         *
+         * @param newQuery The current search query.
+         * @return whether the results changed as a result of the new query.
+         */
+        public boolean onQueryTextChange(String newQuery);
+
+        /**
+         * Method invoked when the search query is submitted, either by dismissing the keyboard,
+         * pressing search or next on the keyboard or when voice has detected the end of the query.
+         *
+         * @param query The query entered.
+         * @return whether the results changed as a result of the query.
+         */
+        public boolean onQueryTextSubmit(String query);
+    }
+
+    private final DataObserver mAdapterObserver = new DataObserver() {
+        @Override
+        public void onChanged() {
+            // onChanged() may be called multiple times e.g. the provider add
+            // rows to ArrayObjectAdapter one by one.
+            mHandler.removeCallbacks(mResultsChangedCallback);
+            mHandler.post(mResultsChangedCallback);
+        }
+    };
+
+    private final Handler mHandler = new Handler();
+
+    private final Runnable mResultsChangedCallback = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
+            if (mRowsSupportFragment != null
+                    && mRowsSupportFragment.getAdapter() != mResultAdapter) {
+                if (!(mRowsSupportFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
+                    mRowsSupportFragment.setAdapter(mResultAdapter);
+                    mRowsSupportFragment.setSelectedPosition(0);
+                }
+            }
+            mStatus |= RESULTS_CHANGED;
+            if ((mStatus & QUERY_COMPLETE) != 0) {
+                updateFocus();
+            }
+            updateSearchBarNextFocusId();
+        }
+    };
+
+    /**
+     * Runs when a new provider is set AND when the fragment view is created.
+     */
+    private final Runnable mSetSearchResultProvider = new Runnable() {
+        @Override
+        public void run() {
+            if (mRowsSupportFragment == null) {
+                // We'll retry once we have a rows fragment
+                return;
+            }
+            // Retrieve the result adapter
+            ObjectAdapter adapter = mProvider.getResultsAdapter();
+            if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
+            if (adapter != mResultAdapter) {
+                boolean firstTime = mResultAdapter == null;
+                releaseAdapter();
+                mResultAdapter = adapter;
+                if (mResultAdapter != null) {
+                    mResultAdapter.registerObserver(mAdapterObserver);
+                }
+                if (DEBUG) Log.v(TAG, "mResultAdapter " + mResultAdapter + " size " +
+                        (mResultAdapter == null ? 0 : mResultAdapter.size()));
+                // delay the first time to avoid setting a empty result adapter
+                // until we got first onChange() from the provider
+                if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
+                    mRowsSupportFragment.setAdapter(mResultAdapter);
+                }
+                executePendingQuery();
+            }
+            updateSearchBarNextFocusId();
+
+            if (DEBUG) Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition +
+                    " mResultAdapter " + mResultAdapter +
+                    " adapter " + mRowsSupportFragment.getAdapter());
+            if (mAutoStartRecognition) {
+                mHandler.removeCallbacks(mStartRecognitionRunnable);
+                mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
+            } else {
+                updateFocus();
+            }
+        }
+    };
+
+    private final Runnable mStartRecognitionRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mAutoStartRecognition = false;
+            mSearchBar.startRecognition();
+        }
+    };
+
+    private RowsSupportFragment mRowsSupportFragment;
+    private SearchBar mSearchBar;
+    private SearchResultProvider mProvider;
+    private String mPendingQuery = null;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private ObjectAdapter mResultAdapter;
+    private SpeechRecognitionCallback mSpeechRecognitionCallback;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private ExternalQuery mExternalQuery;
+
+    private SpeechRecognizer mSpeechRecognizer;
+
+    private int mStatus;
+    private boolean mAutoStartRecognition = true;
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String query) {
+        return createArgs(args, query, null);
+    }
+
+    public static Bundle createArgs(Bundle args, String query, String title)  {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_QUERY, query);
+        args.putString(ARG_TITLE, title);
+        return args;
+    }
+
+    /**
+     * Create a search fragment with a given search query.
+     *
+     * <p>You should only use this if you need to start the search fragment with a
+     * pre-filled query.
+     *
+     * @param query The search query to begin with.
+     * @return A new SearchSupportFragment.
+     */
+    public static SearchSupportFragment newInstance(String query) {
+        SearchSupportFragment fragment = new SearchSupportFragment();
+        Bundle args = createArgs(null, query);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = savedInstanceState == null;
+        }
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
+        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
+        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+            @Override
+            public void onSearchQueryChange(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s %s", query,
+                        null == mProvider ? "(null)" : mProvider));
+                if (null != mProvider) {
+                    retrieveResults(query);
+                } else {
+                    mPendingQuery = query;
+                }
+            }
+
+            @Override
+            public void onSearchQuerySubmit(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
+                submitQuery(query);
+            }
+
+            @Override
+            public void onKeyboardDismiss(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
+                queryComplete();
+            }
+        });
+        mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        applyExternalQuery();
+
+        readArguments(getArguments());
+        if (null != mBadgeDrawable) {
+            setBadgeDrawable(mBadgeDrawable);
+        }
+        if (null != mTitle) {
+            setTitle(mTitle);
+        }
+
+        // Inject the RowsSupportFragment in the results container
+        if (getChildFragmentManager().findFragmentById(R.id.lb_results_frame) == null) {
+            mRowsSupportFragment = new RowsSupportFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.lb_results_frame, mRowsSupportFragment).commit();
+        } else {
+            mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.lb_results_frame);
+        }
+        mRowsSupportFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+                if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
+                mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+                if (null != mOnItemSelectedListener) {
+                    mOnItemSelectedListener.onItemSelected(item, row);
+                }
+                if (null != mOnItemViewSelectedListener) {
+                    mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                            rowViewHolder, row);
+                }
+            }
+        });
+        mRowsSupportFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+            @Override
+            public void onItemClicked(ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+                if (DEBUG) Log.v(TAG, String.format("onItemClicked %d", position));
+                if (null != mOnItemClickedListener) {
+                    mOnItemClickedListener.onItemClicked(item, row);
+                }
+                if (null != mOnItemViewClickedListener) {
+                    mOnItemViewClickedListener.onItemClicked(itemViewHolder, item,
+                            rowViewHolder, row);
+                }
+            }
+        });
+        mRowsSupportFragment.setExpand(true);
+        if (null != mProvider) {
+            onSetSearchResultProvider();
+        }
+        return root;
+    }
+
+    private void resultsAvailable() {
+        if ((mStatus & QUERY_COMPLETE) != 0) {
+            focusOnResults();
+        }
+        updateSearchBarNextFocusId();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        VerticalGridView list = mRowsSupportFragment.getVerticalGridView();
+        int mContainerListAlignTop =
+                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
+        list.setItemAlignmentOffset(0);
+        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignmentOffset(mContainerListAlignTop);
+        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
+            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
+            mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
+        }
+        // Ensure search bar state consistency when using external recognizer
+        mSearchBar.stopRecognition();
+    }
+
+    @Override
+    public void onPause() {
+        releaseRecognizer();
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroy() {
+        releaseAdapter();
+        super.onDestroy();
+    }
+
+    private void releaseRecognizer() {
+        if (null != mSpeechRecognizer) {
+            mSearchBar.setSpeechRecognizer(null);
+            mSpeechRecognizer.destroy();
+            mSpeechRecognizer = null;
+        }
+    }
+
+    /**
+     * Starts speech recognition.  Typical use case is that
+     * activity receives onNewIntent() call when user clicks a MIC button.
+     * Note that SearchSupportFragment automatically starts speech recognition
+     * at first time created, there is no need to call startRecognition()
+     * when fragment is created.
+     */
+    public void startRecognition() {
+        mSearchBar.startRecognition();
+    }
+
+    /**
+     * Set the search provider that is responsible for returning results for the
+     * search query.
+     */
+    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+        if (mProvider != searchResultProvider) {
+            mProvider = searchResultProvider;
+            onSetSearchResultProvider();
+        }
+    }
+
+    /**
+     * Sets an item selection listener for the results.
+     *
+     * @param listener The item selection listener to be invoked when an item in
+     *        the search results is selected.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener for the results.
+     *
+     * @param listener The item clicked listener to be invoked when an item in
+     *        the search results is clicked.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener for the results.
+     *
+     * @param listener The item selection listener to be invoked when an item in
+     *        the search results is selected.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener for the results.
+     *
+     * @param listener The item clicked listener to be invoked when an item in
+     *        the search results is clicked.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+    }
+
+    /**
+     * Sets the title string to be be shown in an empty search bar. The title
+     * may be placed in a call-to-action, such as "Search <i>title</i>" or
+     * "Speak to search <i>title</i>".
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (null != mSearchBar) {
+            mSearchBar.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title set in the search bar.
+     */
+    public String getTitle() {
+        if (null != mSearchBar) {
+            return mSearchBar.getTitle();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the badge drawable that will be shown inside the search bar next to
+     * the title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        if (null != mSearchBar) {
+            mSearchBar.setBadgeDrawable(drawable);
+        }
+    }
+
+    /**
+     * Returns the badge drawable in the search bar.
+     */
+    public Drawable getBadgeDrawable() {
+        if (null != mSearchBar) {
+            return mSearchBar.getBadgeDrawable();
+        }
+        return null;
+    }
+
+    /**
+     * Display the completions shown by the IME. An application may provide
+     * a list of query completions that the system will show in the IME.
+     *
+     * @param completions A list of completions to show in the IME. Setting to
+     *        null or empty will clear the list.
+     */
+    public void displayCompletions(List<String> completions) {
+        mSearchBar.displayCompletions(completions);
+    }
+
+    /**
+     * Set this callback to have the fragment pass speech recognition requests
+     * to the activity rather than using an internal recognizer.
+     */
+    public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
+        mSpeechRecognitionCallback = callback;
+        if (mSearchBar != null) {
+            mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        }
+        if (callback != null) {
+            releaseRecognizer();
+        }
+    }
+
+    /**
+     * Sets the text of the search query and optionally submits the query. Either
+     * {@link SearchResultProvider#onQueryTextChange onQueryTextChange} or
+     * {@link SearchResultProvider#onQueryTextSubmit onQueryTextSubmit} will be
+     * called on the provider if it is set.
+     *
+     * @param query The search query to set.
+     * @param submit Whether to submit the query.
+     */
+    public void setSearchQuery(String query, boolean submit) {
+        if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
+        if (query == null) {
+            return;
+        }
+        mExternalQuery = new ExternalQuery(query, submit);
+        applyExternalQuery();
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = false;
+            mHandler.removeCallbacks(mStartRecognitionRunnable);
+        }
+    }
+
+    /**
+     * Sets the text of the search query based on the {@link RecognizerIntent#EXTRA_RESULTS} in
+     * the given intent, and optionally submit the query.  If more than one result is present
+     * in the results list, the first will be used.
+     *
+     * @param intent Intent received from a speech recognition service.
+     * @param submit Whether to submit the query.
+     */
+    public void setSearchQuery(Intent intent, boolean submit) {
+        ArrayList<String> matches = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
+        if (matches != null && matches.size() > 0) {
+            setSearchQuery(matches.get(0), submit);
+        }
+    }
+
+    /**
+     * Returns an intent that can be used to request speech recognition.
+     * Built from the base {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} plus
+     * extras:
+     *
+     * <ul>
+     * <li>{@link RecognizerIntent#EXTRA_LANGUAGE_MODEL} set to
+     * {@link RecognizerIntent#LANGUAGE_MODEL_FREE_FORM}</li>
+     * <li>{@link RecognizerIntent#EXTRA_PARTIAL_RESULTS} set to true</li>
+     * <li>{@link RecognizerIntent#EXTRA_PROMPT} set to the search bar hint text</li>
+     * </ul>
+     *
+     * For handling the intent returned from the service, see
+     * {@link #setSearchQuery(Intent, boolean)}.
+     */
+    public Intent getRecognizerIntent() {
+        Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
+        if (mSearchBar != null && mSearchBar.getHint() != null) {
+            recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
+        }
+        recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
+        return recognizerIntent;
+    }
+
+    private void retrieveResults(String searchQuery) {
+        if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
+        if (mProvider.onQueryTextChange(searchQuery)) {
+            mStatus &= ~QUERY_COMPLETE;
+        }
+    }
+
+    private void submitQuery(String query) {
+        queryComplete();
+        if (null != mProvider) {
+            mProvider.onQueryTextSubmit(query);
+        }
+    }
+
+    private void queryComplete() {
+        if (DEBUG) Log.v(TAG, "queryComplete");
+        mStatus |= QUERY_COMPLETE;
+        focusOnResults();
+    }
+
+    private void updateSearchBarNextFocusId() {
+        if (mSearchBar == null || mResultAdapter == null) {
+            return;
+        }
+        final int viewId = (mResultAdapter.size() == 0 || mRowsSupportFragment == null ||
+                mRowsSupportFragment.getVerticalGridView() == null) ? 0 :
+                mRowsSupportFragment.getVerticalGridView().getId();
+        mSearchBar.setNextFocusDownId(viewId);
+    }
+
+    private void updateFocus() {
+        if (mResultAdapter != null && mResultAdapter.size() > 0 &&
+                mRowsSupportFragment != null && mRowsSupportFragment.getAdapter() == mResultAdapter) {
+            focusOnResults();
+        } else {
+            mSearchBar.requestFocus();
+        }
+    }
+
+    private void focusOnResults() {
+        if (mRowsSupportFragment == null ||
+                mRowsSupportFragment.getVerticalGridView() == null ||
+                mResultAdapter.size() == 0) {
+            return;
+        }
+        if (mRowsSupportFragment.getVerticalGridView().requestFocus()) {
+            mStatus &= ~RESULTS_CHANGED;
+        }
+    }
+
+    private void onSetSearchResultProvider() {
+        mHandler.removeCallbacks(mSetSearchResultProvider);
+        mHandler.post(mSetSearchResultProvider);
+    }
+
+    private void releaseAdapter() {
+        if (mResultAdapter != null) {
+            mResultAdapter.unregisterObserver(mAdapterObserver);
+            mResultAdapter = null;
+        }
+    }
+
+    private void executePendingQuery() {
+        if (null != mPendingQuery && null != mResultAdapter) {
+            String query = mPendingQuery;
+            mPendingQuery = null;
+            retrieveResults(query);
+        }
+    }
+
+    private void applyExternalQuery() {
+        if (mExternalQuery == null || mSearchBar == null) {
+            return;
+        }
+        mSearchBar.setSearchQuery(mExternalQuery.mQuery);
+        if (mExternalQuery.mSubmit) {
+            submitQuery(mExternalQuery.mQuery);
+        }
+        mExternalQuery = null;
+    }
+
+    private void readArguments(Bundle args) {
+        if (null == args) {
+            return;
+        }
+        if (args.containsKey(ARG_QUERY)) {
+            setSearchQuery(args.getString(ARG_QUERY));
+        }
+
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+    }
+
+    private void setSearchQuery(String query) {
+        mSearchBar.setSearchQuery(query);
+    }
+
+    static class ExternalQuery {
+        String mQuery;
+        boolean mSubmit;
+
+        ExternalQuery(String query, boolean submit) {
+            mQuery = query;
+            mSubmit = submit;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
deleted file mode 100644
index 69d80fe..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.SlideCallback;
-import android.view.View;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-class TitleTransitionHelper {
-
-    final static SlideCallback sSlideCallback = new SlideCallback() {
-        @Override
-        public boolean getSlide(View view, boolean appear, int[] edge, float[] distance) {
-            edge[0] = TransitionHelper.SLIDE_TOP;
-            distance[0] = view.getHeight();
-            return true;
-        }
-    };
-
-    private static Interpolator createTransitionInterpolatorUp() {
-        return new DecelerateInterpolator(4);
-    }
-
-    private static Interpolator createTransitionInterpolatorDown() {
-        return new DecelerateInterpolator();
-    }
-
-    static public Object createTransitionTitleUp(TransitionHelper helper) {
-        Object transition = helper.createSlide(sSlideCallback);
-        helper.setInterpolator(transition, createTransitionInterpolatorUp());
-        return transition;
-    }
-
-    static public Object createTransitionTitleDown(TransitionHelper helper) {
-        Object transition = helper.createSlide(sSlideCallback);
-        helper.setInterpolator(transition, createTransitionInterpolatorDown());
-        return transition;
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 4ee594b..d75c327 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -15,6 +15,8 @@
 
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.OnChildLaidOutListener;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.Presenter;
@@ -26,7 +28,9 @@
 import android.support.v17.leanback.widget.OnItemClickedListener;
 import android.support.v17.leanback.widget.OnItemSelectedListener;
 import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
 import android.app.Fragment;
+import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.Log;
@@ -116,7 +120,7 @@
             throw new IllegalArgumentException("Grid presenter may not be null");
         }
         mGridPresenter = gridPresenter;
-        mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener);
+        mGridPresenter.setOnItemViewSelectedListener(mViewSelectedListener);
         if (mOnItemViewClickedListener != null) {
             mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
         }
@@ -147,14 +151,14 @@
         return mAdapter;
     }
 
-    final private OnItemViewSelectedListener mRowSelectedListener =
+    final private OnItemViewSelectedListener mViewSelectedListener =
             new OnItemViewSelectedListener() {
         @Override
         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                 RowPresenter.ViewHolder rowViewHolder, Row row) {
             int position = mGridViewHolder.getGridView().getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "row selected position " + position);
-            onRowSelected(position);
+            if (DEBUG) Log.v(TAG, "grid selected position " + position);
+            gridOnItemSelected(position);
             if (mOnItemSelectedListener != null) {
                 mOnItemSelectedListener.onItemSelected(item, row);
             }
@@ -165,6 +169,16 @@
         }
     };
 
+    final private OnChildLaidOutListener mChildLaidOutListener =
+            new OnChildLaidOutListener() {
+        @Override
+        public void onChildLaidOut(ViewGroup parent, View view, int position, long id) {
+            if (position == 0) {
+                showOrHideTitle();
+            }
+        }
+    };
+
     /**
      * Sets an item selection listener.
      * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
@@ -180,19 +194,27 @@
         mOnItemViewSelectedListener = listener;
     }
 
-    private void onRowSelected(int position) {
+    private void gridOnItemSelected(int position) {
         if (position != mSelectedPosition) {
-            if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
-                // if has no sibling in front of it,  show title
-                if (!mShowingTitle) {
-                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
-                    mShowingTitle = true;
-                }
-            } else if (mShowingTitle) {
-                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
-                mShowingTitle = false;
-            }
             mSelectedPosition = position;
+            showOrHideTitle();
+        }
+    }
+
+    private void showOrHideTitle() {
+        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
+                == null) {
+            return;
+        }
+        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
+            // if has no sibling in front of it,  show title
+            if (!mShowingTitle) {
+                sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+                mShowingTitle = true;
+            }
+        } else if (mShowingTitle) {
+            sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+            mShowingTitle = false;
         }
     }
 
@@ -300,8 +322,11 @@
             if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
 
             final View searchOrbView = mTitleView.getSearchAffordanceView();
+            final boolean isRtl = ViewCompat.getLayoutDirection(focused) ==
+                    View.LAYOUT_DIRECTION_RTL;
+            final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
             if (focused == searchOrbView && (
-                    direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
+                    direction == View.FOCUS_DOWN || direction == forward)) {
                 return mGridViewHolder.view;
 
             } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
@@ -345,10 +370,9 @@
                 mTitleView.setVisibility(View.INVISIBLE);
             }
         });
-        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
-        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
-        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_grid_dock, true);
-        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_grid_dock, true);
+        Context context = getActivity();
+        mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out);
+        mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in);
 
         return root;
     }
@@ -358,6 +382,7 @@
         ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
         mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
         gridDock.addView(mGridViewHolder.view);
+        mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
 
         updateAdapter();
     }
@@ -369,6 +394,18 @@
     }
 
     @Override
+    public void onPause() {
+        mTitleView.enableAnimation(false);
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mTitleView.enableAnimation(true);
+    }
+
+    @Override
     public void onDestroyView() {
         super.onDestroyView();
         mGridViewHolder = null;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
new file mode 100644
index 0000000..3cb919a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -0,0 +1,434 @@
+/* This file is auto-generated from VerticalGridFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.OnChildLaidOutListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.app.Fragment;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A fragment for creating leanback vertical grids.
+ *
+ * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
+ * an {@link ObjectAdapter}.
+ */
+public class VerticalGridSupportFragment extends Fragment {
+    private static final String TAG = "VerticalGridSupportFragment";
+    private static boolean DEBUG = false;
+
+    private BrowseFrameLayout mBrowseFrame;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private ObjectAdapter mAdapter;
+    private VerticalGridPresenter mGridPresenter;
+    private VerticalGridPresenter.ViewHolder mGridViewHolder;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private View.OnClickListener mExternalOnSearchClickedListener;
+    private int mSelectedPosition = -1;
+
+    private TitleView mTitleView;
+    private SearchOrbView.Colors mSearchAffordanceColors;
+    private boolean mSearchAffordanceColorSet;
+    private boolean mShowingTitle = true;
+
+    // transition related
+    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+    private Object mTitleUpTransition;
+    private Object mTitleDownTransition;
+    private Object mSceneWithTitle;
+    private Object mSceneWithoutTitle;
+
+    /**
+     * Sets the badge drawable displayed in the title area.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (drawable != mBadgeDrawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setTitle(mTitle);
+        }
+    }
+
+    /**
+     * Returns the title for the fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the grid presenter.
+     */
+    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+        if (gridPresenter == null) {
+            throw new IllegalArgumentException("Grid presenter may not be null");
+        }
+        mGridPresenter = gridPresenter;
+        mGridPresenter.setOnItemViewSelectedListener(mViewSelectedListener);
+        if (mOnItemViewClickedListener != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        if (mOnItemClickedListener != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the grid presenter.
+     */
+    public VerticalGridPresenter getGridPresenter() {
+        return mGridPresenter;
+    }
+
+    /**
+     * Sets the object adapter for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the object adapter.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    final private OnItemViewSelectedListener mViewSelectedListener =
+            new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mGridViewHolder.getGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "grid selected position " + position);
+            gridOnItemSelected(position);
+            if (mOnItemSelectedListener != null) {
+                mOnItemSelectedListener.onItemSelected(item, row);
+            }
+            if (mOnItemViewSelectedListener != null) {
+                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    final private OnChildLaidOutListener mChildLaidOutListener =
+            new OnChildLaidOutListener() {
+        @Override
+        public void onChildLaidOut(ViewGroup parent, View view, int position, long id) {
+            if (position == 0) {
+                showOrHideTitle();
+            }
+        }
+    };
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    private void gridOnItemSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSelectedPosition = position;
+            showOrHideTitle();
+        }
+    }
+
+    private void showOrHideTitle() {
+        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
+                == null) {
+            return;
+        }
+        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
+            // if has no sibling in front of it,  show title
+            if (!mShowingTitle) {
+                sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+                mShowingTitle = true;
+            }
+        } else if (mShowingTitle) {
+            sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+            mShowingTitle = false;
+        }
+    }
+
+    /**
+     * Sets an item clicked listener.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Sets a click listener for the search affordance.
+     *
+     * <p>The presence of a listener will change the visibility of the search
+     * affordance in the title area. When set to non-null, the title area will
+     * contain a call to search action.
+     *
+     * <p>The listener's onClick method will be invoked when the user clicks on
+     * the search action.
+     *
+     * @param listener The listener to invoke when the search affordance is
+     *        clicked, or null to hide the search affordance.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mTitleView != null) {
+            mTitleView.setOnSearchClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchAffordanceColors = colors;
+        mSearchAffordanceColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+    }
+
+    /**
+     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColors;
+        }
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColors();
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     * A default brighter color will be set by the framework.
+     *
+     * @param color The color to use for the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        setSearchAffordanceColors(new SearchOrbView.Colors(color));
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return getSearchAffordanceColors().color;
+    }
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
+            final boolean isRtl = ViewCompat.getLayoutDirection(focused) ==
+                    View.LAYOUT_DIRECTION_RTL;
+            final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+            if (focused == searchOrbView && (
+                    direction == View.FOCUS_DOWN || direction == forward)) {
+                return mGridViewHolder.view;
+
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+
+            } else {
+                return null;
+            }
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
+                container, false);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        mTitleView.setTitle(mTitle);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+        if (mExternalOnSearchClickedListener != null) {
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+        }
+
+        mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
+            @Override
+            public void run() {
+                mTitleView.setVisibility(View.VISIBLE);
+            }
+        });
+        mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
+            @Override
+            public void run() {
+                mTitleView.setVisibility(View.INVISIBLE);
+            }
+        });
+        Context context = getActivity();
+        mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out);
+        mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in);
+
+        return root;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
+        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+        gridDock.addView(mGridViewHolder.view);
+        mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
+
+        updateAdapter();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mGridViewHolder.getGridView().requestFocus();
+    }
+
+    @Override
+    public void onPause() {
+        mTitleView.enableAnimation(false);
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mTitleView.enableAnimation(true);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mGridViewHolder = null;
+    }
+
+    /**
+     * Sets the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+        }
+    }
+
+    private void updateAdapter() {
+        if (mGridViewHolder != null) {
+            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+            if (mSelectedPosition != -1) {
+                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java b/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java
new file mode 100644
index 0000000..09a082dd
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.support.v17.leanback.os;
+
+import android.os.Build;
+import android.support.v17.leanback.os.TraceHelperJbmr2;
+
+
+/**
+ * Helper for systrace events.
+ * @hide
+ */
+public final class TraceHelper {
+
+    final static TraceHelperVersionImpl sImpl;
+
+    static interface TraceHelperVersionImpl {
+        public void beginSection(String section);
+        public void endSection();
+    }
+
+    private static final class TraceHelperStubImpl implements TraceHelperVersionImpl {
+        @Override
+        public void beginSection(String section) {
+        }
+
+        @Override
+        public void endSection() {
+        }
+    }
+
+    private static final class TraceHelperJbmr2Impl implements TraceHelperVersionImpl {
+        @Override
+        public void beginSection(String section) {
+            TraceHelperJbmr2.beginSection(section);
+        }
+
+        @Override
+        public void endSection() {
+            TraceHelperJbmr2.endSection();
+        }
+    }
+
+    private TraceHelper() {
+    }
+
+    static {
+        if (Build.VERSION.SDK_INT >= 18) {
+            sImpl = new TraceHelperJbmr2Impl();
+        } else {
+            sImpl = new TraceHelperStubImpl();
+        }
+    }
+
+    public static void beginSection(String section) {
+        sImpl.beginSection(section);
+    }
+
+    public static void endSection() {
+        sImpl.endSection();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
new file mode 100644
index 0000000..f7451d4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.AnimatorInflater;
+import android.content.Context;
+import android.os.Build;
+import android.support.v17.leanback.R;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * Helper class to load Leanback specific transition.
+ * @hide
+ */
+public class LeanbackTransitionHelper {
+
+    static interface LeanbackTransitionHelperVersion {
+
+        public Object loadTitleInTransition(Context context, TransitionHelper helper);
+
+        public Object loadTitleOutTransition(Context context, TransitionHelper helper);
+    }
+
+    /*
+     * Kitkat does not allow load custom transition from resource, calling
+     * LeanbackTransitionHelperKitKat to build custom transition in code.
+     */
+    static class LeanbackTransitionHelperKitKatImpl implements LeanbackTransitionHelperVersion {
+
+        @Override
+        public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+            return LeanbackTransitionHelperKitKat.loadTitleInTransition(context);
+        }
+
+        @Override
+        public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+            return LeanbackTransitionHelperKitKat.loadTitleOutTransition(context);
+        }
+    }
+
+    /*
+     * Load transition from resource or just return stub for API17.
+     */
+    static class LeanbackTransitionHelperDefault implements LeanbackTransitionHelperVersion {
+
+        @Override
+        public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+            return helper.loadTransition(context, R.transition.lb_title_in);
+        }
+
+        @Override
+        public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+            return helper.loadTransition(context, R.transition.lb_title_out);
+        }
+    }
+
+    static LeanbackTransitionHelperVersion sImpl;
+
+    static {
+        if (Build.VERSION.SDK_INT >= 21) {
+            sImpl = new LeanbackTransitionHelperDefault();
+        } else if (Build.VERSION.SDK_INT >= 19) {
+            sImpl = new LeanbackTransitionHelperKitKatImpl();
+        } else {
+            // Helper will create a stub object for transition in this case.
+            sImpl = new LeanbackTransitionHelperDefault();
+        }
+    }
+
+    static public Object loadTitleInTransition(Context context, TransitionHelper helper) {
+        return sImpl.loadTitleInTransition(context, helper);
+    }
+
+    static public Object loadTitleOutTransition(Context context, TransitionHelper helper) {
+        return sImpl.loadTitleOutTransition(context, helper);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
index 2e17119..1c66d03 100644
--- a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
@@ -15,6 +15,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -28,10 +29,10 @@
     public static final int FADE_IN = 0x1;
     public static final int FADE_OUT = 0x2;
 
-    public static final int SLIDE_LEFT = 0;
-    public static final int SLIDE_TOP = 1;
-    public static final int SLIDE_RIGHT = 2;
-    public static final int SLIDE_BOTTOM = 3;
+    public static final int SLIDE_LEFT = Gravity.LEFT;
+    public static final int SLIDE_TOP = Gravity.TOP;
+    public static final int SLIDE_RIGHT = Gravity.RIGHT;
+    public static final int SLIDE_BOTTOM = Gravity.BOTTOM;
 
     private final static TransitionHelper sHelper = new TransitionHelper();
     TransitionHelperVersionImpl mImpl;
@@ -50,6 +51,13 @@
     }
 
     /**
+     * Returns true if system supports entrance Transition animations.
+     */
+    public static boolean systemSupportsEntranceTransitions() {
+        return Build.VERSION.SDK_INT >= 21;
+    }
+
+    /**
      * Interface implemented by classes that support Transition animations.
      */
     static interface TransitionHelperVersionImpl {
@@ -74,7 +82,7 @@
 
         public Object createAutoTransition();
 
-        public Object createSlide(SlideCallback callback);
+        public Object createSlide(int slideEdge);
 
         public Object createScale();
 
@@ -120,6 +128,8 @@
         public void addTarget(Object transition, View view);
 
         public Object createDefaultInterpolator(Context context);
+
+        public Object loadTransition(Context context, int resId);
     }
 
     /**
@@ -192,7 +202,7 @@
         }
 
         @Override
-        public Object createSlide(SlideCallback callback) {
+        public Object createSlide(int slideEdge) {
             return new TransitionStub();
         }
 
@@ -291,6 +301,11 @@
         public Object createDefaultInterpolator(Context context) {
             return null;
         }
+
+        @Override
+        public Object loadTransition(Context context, int resId) {
+            return new TransitionStub();
+        }
     }
 
     /**
@@ -359,8 +374,8 @@
         }
 
         @Override
-        public Object createSlide(SlideCallback callback) {
-            return TransitionHelperKitkat.createSlide(callback);
+        public Object createSlide(int slideEdge) {
+            return TransitionHelperKitkat.createSlide(slideEdge);
         }
 
         @Override
@@ -463,6 +478,11 @@
         public Object createDefaultInterpolator(Context context) {
             return null;
         }
+
+        @Override
+        public Object loadTransition(Context context, int resId) {
+            return TransitionHelperKitkat.loadTransition(context, resId);
+        }
     }
 
     private static final class TransitionHelperApi21Impl extends TransitionHelperKitkatImpl {
@@ -596,8 +616,8 @@
         return mImpl.createTransitionSet(sequential);
     }
 
-    public Object createSlide(SlideCallback callback) {
-        return mImpl.createSlide(callback);
+    public Object createSlide(int slideEdge) {
+        return mImpl.createSlide(slideEdge);
     }
 
     public Object createScale() {
@@ -667,4 +687,8 @@
     public Object createDefaultInterpolator(Context context) {
         return mImpl.createDefaultInterpolator(context);
     }
+
+    public Object loadTransition(Context context, int resId) {
+        return mImpl.loadTransition(context, resId);
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
index 1fd6ea2..3bd4661 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
@@ -20,6 +20,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.widget.TextView;
 
 /**
@@ -45,8 +46,9 @@
         private final FontMetricsInt mTitleFontMetricsInt;
         private final FontMetricsInt mSubtitleFontMetricsInt;
         private final FontMetricsInt mBodyFontMetricsInt;
+        private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
 
-        public ViewHolder(View view) {
+        public ViewHolder(final View view) {
             super(view);
             mTitle = (TextView) view.findViewById(R.id.lb_details_description_title);
             mSubtitle = (TextView) view.findViewById(R.id.lb_details_description_subtitle);
@@ -79,13 +81,41 @@
 
             mTitle.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                 @Override
-                public void onLayoutChange(View v, int left, int top, int right,
-                        int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    mBody.setMaxLines(mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines);
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                                           int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    addPreDrawListener();
                 }
             });
         }
 
+        void addPreDrawListener() {
+            if (mPreDrawListener != null) {
+                return;
+            }
+            mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    final int titleLines = mTitle.getLineCount();
+                    final int maxLines = titleLines > 1 ? mBodyMinLines : mBodyMaxLines;
+                    if (mBody.getMaxLines() != maxLines) {
+                        mBody.setMaxLines(maxLines);
+                        return false;
+                    } else {
+                        removePreDrawListener();
+                        return true;
+                    }
+                }
+            };
+            view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
+        }
+
+        void removePreDrawListener() {
+            if (mPreDrawListener != null) {
+                view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
+                mPreDrawListener = null;
+            }
+        }
+
         public TextView getTitle() {
             return mTitle;
         }
@@ -174,6 +204,22 @@
     @Override
     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
 
+    @Override
+    public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
+        // In case predraw listener was removed in detach, make sure
+        // we have the proper layout.
+        ViewHolder vh = (ViewHolder) holder;
+        vh.addPreDrawListener();
+        super.onViewAttachedToWindow(holder);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
+        ViewHolder vh = (ViewHolder) holder;
+        vh.removePreDrawListener();
+        super.onViewDetachedFromWindow(holder);
+    }
+
     private void setTopMargin(TextView textView, int topMargin) {
         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
         lp.topMargin = topMargin;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
index bedca43..bd5fa62 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -13,6 +13,7 @@
  */
 package android.support.v17.leanback.widget;
 
+import android.graphics.drawable.Drawable;
 import android.support.v17.leanback.R;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -38,10 +39,12 @@
     static class ActionViewHolder extends Presenter.ViewHolder {
         Action mAction;
         Button mButton;
+        int mLayoutDirection;
 
-        public ActionViewHolder(View view) {
+        public ActionViewHolder(View view, int layoutDirection) {
             super(view);
             mButton = (Button) view.findViewById(R.id.lb_action_button);
+            mLayoutDirection = layoutDirection;
         }
     }
 
@@ -50,7 +53,7 @@
         public ViewHolder onCreateViewHolder(ViewGroup parent) {
             View v = LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.lb_action_1_line, parent, false);
-            return new ActionViewHolder(v);
+            return new ActionViewHolder(v, parent.getLayoutDirection());
         }
 
         @Override
@@ -72,27 +75,32 @@
         public ViewHolder onCreateViewHolder(ViewGroup parent) {
             View v = LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.lb_action_2_lines, parent, false);
-            return new ActionViewHolder(v);
+            return new ActionViewHolder(v, parent.getLayoutDirection());
         }
 
         @Override
         public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
             Action action = (Action) item;
             ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            Drawable icon = action.getIcon();
             vh.mAction = action;
 
-            if (action.getIcon() != null) {
-                final int leftPadding = vh.view.getResources()
-                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_left);
-                final int rightPadding = vh.view.getResources()
-                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_right);
-                vh.view.setPadding(leftPadding, 0, rightPadding, 0);
+            if (icon != null) {
+                final int startPadding = vh.view.getResources()
+                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
+                final int endPadding = vh.view.getResources()
+                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
+                vh.view.setPaddingRelative(startPadding, 0, endPadding, 0);
             } else {
                 final int padding = vh.view.getResources()
                         .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
-                vh.view.setPadding(padding, 0, padding, 0);
+                vh.view.setPaddingRelative(padding, 0, padding, 0);
             }
-            vh.mButton.setCompoundDrawablesWithIntrinsicBounds(action.getIcon(), null, null, null);
+            if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
+            } else {
+                vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+            }
 
             CharSequence line1 = action.getLabel1();
             CharSequence line2 = action.getLabel2();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 6c4ee28..19803ca 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -15,6 +15,8 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * An ObjectAdapter implemented with an {@link ArrayList}.
@@ -88,6 +90,7 @@
 
     /**
      * Inserts an item into this adapter at the specified index.
+     * If the index is >= {@link #size} an exception will be thrown.
      *
      * @param index The index at which the item should be inserted.
      * @param item The item to insert into the adapter.
@@ -99,13 +102,16 @@
 
     /**
      * Adds the objects in the given collection to the adapter, starting at the
-     * given index.
+     * given index.  If the index is >= {@link #size} an exception will be thrown.
      *
      * @param index The index at which the items should be inserted.
      * @param items A {@link Collection} of items to insert.
      */
     public void addAll(int index, Collection items) {
         int itemsCount = items.size();
+        if (itemsCount == 0) {
+            return;
+        }
         mItems.addAll(index, items);
         notifyItemRangeInserted(index, itemsCount);
     }
@@ -126,6 +132,18 @@
     }
 
     /**
+     * Replaces item at position with a new item and calls notifyItemRangeChanged()
+     * at the given position.  Note that this method does not compare new item to
+     * existing item.
+     * @param position  The index of item to replace.
+     * @param item      The new item to be placed at given position.
+     */
+    public void replace(int position, Object item) {
+        mItems.set(position, item);
+        notifyItemRangeChanged(position, 1);
+    }
+
+    /**
      * Removes a range of items from the adapter. The range is specified by giving
      * the starting position and the number of elements to remove.
      *
@@ -135,6 +153,9 @@
      */
     public int removeItems(int position, int count) {
         int itemsToRemove = Math.min(count, mItems.size() - position);
+        if (itemsToRemove <= 0) {
+            return 0;
+        }
 
         for (int i = 0; i < itemsToRemove; i++) {
             mItems.remove(position);
@@ -148,7 +169,17 @@
      */
     public void clear() {
         int itemCount = mItems.size();
+        if (itemCount == 0) {
+            return;
+        }
         mItems.clear();
         notifyItemRangeRemoved(0, itemCount);
     }
+
+    /**
+     * Gets a read-only view of the list of object of this ArrayObjectAdapter.
+     */
+    public <E> List<E> unmodifiableList() {
+        return Collections.unmodifiableList((List<E>) mItems);
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 94eee28..4743f6a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -55,6 +55,10 @@
      * navigating away from the first item, the focus maintains a middle
      * location.
      * <p>
+     * For HorizontalGridView, low edge refers to left edge when RTL is false or
+     * right edge when RTL is true.
+     * For VerticalGridView, low edge refers to top edge.
+     * <p>
      * The middle location is calculated by "windowAlignOffset" and
      * "windowAlignOffsetPercent"; if neither of these two is defined, the
      * default value is 1/2 of the size.
@@ -66,6 +70,10 @@
      * navigating to the end of list. When navigating away from the end, the
      * focus maintains a middle location.
      * <p>
+     * For HorizontalGridView, high edge refers to right edge when RTL is false or
+     * left edge when RTL is true.
+     * For VerticalGridView, high edge refers to bottom edge.
+     * <p>
      * The middle location is calculated by "windowAlignOffset" and
      * "windowAlignOffsetPercent"; if neither of these two is defined, the
      * default value is 1/2 of the size.
@@ -169,6 +177,7 @@
     private OnTouchInterceptListener mOnTouchInterceptListener;
     private OnMotionInterceptListener mOnMotionInterceptListener;
     private OnKeyInterceptListener mOnKeyInterceptListener;
+    private RecyclerView.RecyclerListener mChainedRecyclerListener;
 
     public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -179,6 +188,19 @@
         setChildrenDrawingOrderEnabled(true);
         setWillNotDraw(true);
         setOverScrollMode(View.OVER_SCROLL_NEVER);
+        // Disable change animation by default on leanback.
+        // Change animation will create a new view and cause undesired
+        // focus animation between the old view and new view.
+        getItemAnimator().setSupportsChangeAnimations(false);
+        super.setRecyclerListener(new RecyclerView.RecyclerListener() {
+            @Override
+            public void onViewRecycled(RecyclerView.ViewHolder holder) {
+                mLayoutManager.onChildRecycled(holder);
+                if (mChainedRecyclerListener != null) {
+                    mChainedRecyclerListener.onViewRecycled(holder);
+                }
+            }
+        });
     }
 
     protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
@@ -248,11 +270,13 @@
     }
 
     /**
-     * Set the absolute offset in pixels for window alignment.
+     * Set the offset in pixels for window alignment.
      *
-     * @param offset The number of pixels to offset. Can be negative for
-     *        alignment from the high edge, or positive for alignment from the
-     *        low edge.
+     * @param offset The number of pixels to offset.  If the offset is positive,
+     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+     *        if the offset is negative, the absolute value is distance from high
+     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+     *        Default value is 0.
      */
     public void setWindowAlignmentOffset(int offset) {
         mLayoutManager.setWindowAlignmentOffset(offset);
@@ -260,11 +284,13 @@
     }
 
     /**
-     * Get the absolute offset in pixels for window alignment.
+     * Get the offset in pixels for window alignment.
      *
-     * @return The number of pixels to offset. Will be negative for alignment
-     *         from the high edge, or positive for alignment from the low edge.
-     *         Default value is 0.
+     * @return The number of pixels to offset.  If the offset is positive,
+     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+     *        if the offset is negative, the absolute value is distance from high
+     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+     *        Default value is 0.
      */
     public int getWindowAlignmentOffset() {
         return mLayoutManager.getWindowAlignmentOffset();
@@ -277,6 +303,7 @@
      * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
      *        width from low edge. Use
      *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     *         Default value is 50.
      */
     public void setWindowAlignmentOffsetPercent(float offsetPercent) {
         mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
@@ -416,6 +443,16 @@
 
     /**
      * Register a callback to be invoked when an item in BaseGridView has
+     * been laid out.
+     *
+     * @param listener The listener to be invoked.
+     */
+    public void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
+        mLayoutManager.setOnChildLaidOutListener(listener);
+    }
+
+    /**
+     * Register a callback to be invoked when an item in BaseGridView has
      * been selected.  Note that the listener may be invoked when there is a
      * layout pending on the view, affording the listener an opportunity to
      * adjust the upcoming layout based on the selection state.
@@ -476,7 +513,7 @@
 
     /**
      * Describes how the child views are positioned. Defaults to
-     * GRAVITY_TOP|GRAVITY_LEFT.
+     * GRAVITY_TOP|GRAVITY_START.
      *
      * @param gravity See {@link android.view.Gravity}
      */
@@ -516,6 +553,9 @@
      * Disable or enable focus search.
      */
     public final void setFocusSearchDisabled(boolean disabled) {
+        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
+        // re-gain focus after a BACK key pressed, so block children focus during transition.
+        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
         mLayoutManager.setFocusSearchDisabled(disabled);
     }
 
@@ -564,7 +604,9 @@
 
     /**
      * Returns true if the view at the given position has a same row sibling
-     * in front of it.
+     * in front of it.  This will return true if first item view is not created.
+     * So application should check in both {@link OnChildSelectedListener} and {@link
+     * OnChildLaidOutListener}.
      *
      * @param position Position in adapter.
      */
@@ -669,16 +711,6 @@
         mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
     }
 
-    /**
-     * Set the factor by which children should be laid out beyond the view bounds
-     * in the direction of orientation.  1.0 disables over reach.
-     *
-     * @param fraction fraction of over reach
-     */
-    public final void setPrimaryOverReach(float fraction) {
-        mLayoutManager.setPrimaryOverReach(fraction);
-    }
-
     @Override
     public boolean hasOverlappingRendering() {
         return mHasOverlappingRendering;
@@ -687,4 +719,17 @@
     public void setHasOverlappingRendering(boolean hasOverlapping) {
         mHasOverlappingRendering = hasOverlapping;
     }
+
+    /**
+     * Notify layout manager that layout directionality has been updated
+     */
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
+    }
+
+    @Override
+    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
+        mChainedRecyclerListener = listener;
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
similarity index 96%
rename from v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
rename to v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
index 9b87305..654f39b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package android.support.v17.leanback.app;
+package android.support.v17.leanback.widget;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -22,9 +22,9 @@
 /**
  * Top level implementation viewgroup for browse to manage transitions between
  * browse sub fragments.
- *
+ * @hide
  */
-class BrowseFrameLayout extends FrameLayout {
+public class BrowseFrameLayout extends FrameLayout {
 
     public interface OnFocusSearchListener {
         public View onFocusSearch(View focused, int direction);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
similarity index 94%
rename from v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
rename to v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
index 3f10a63..6b663ce 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
@@ -11,7 +11,7 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package android.support.v17.leanback.app;
+package android.support.v17.leanback.widget;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -22,8 +22,9 @@
  * Customized FrameLayout excludes margin of child from calculating the child size.
  * So we can change left margin of rows while keep the width of rows unchanged without
  * using hardcoded DIPS.
+ * @hide
  */
-class BrowseRowsFrameLayout extends FrameLayout {
+public class BrowseRowsFrameLayout extends FrameLayout {
 
     public BrowseRowsFrameLayout(Context context) {
         this(context ,null);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
index 2434f98..3f4ee55 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
@@ -122,21 +122,22 @@
 
         void showControls(Presenter presenter) {
             ObjectAdapter adapter = getDisplayedAdapter();
+            int adapterSize = adapter == null ? 0 : adapter.size();
             // Shrink the number of attached views
             View focusedView = mControlBar.getFocusedChild();
-            if (focusedView != null && adapter.size() > 0 &&
-                    mControlBar.indexOfChild(focusedView) >= adapter.size()) {
+            if (focusedView != null && adapterSize > 0 &&
+                    mControlBar.indexOfChild(focusedView) >= adapterSize) {
                 mControlBar.getChildAt(adapter.size() - 1).requestFocus();
             }
-            for (int i = mControlBar.getChildCount() - 1; i >= adapter.size(); i--) {
+            for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
                 mControlBar.removeViewAt(i);
             }
-            for (int position = 0; position < adapter.size() && position < MAX_CONTROLS;
+            for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
                     position++) {
                 bindControlToAction(position, adapter, presenter);
             }
             mControlBar.setChildMarginFromCenter(
-                    getChildMarginFromCenter(mControlBar.getContext(), adapter.size()));
+                    getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
         }
 
         void bindControlToAction(int position, Presenter presenter) {
@@ -241,7 +242,9 @@
         BoundData data = (BoundData) item;
         if (vh.mAdapter != data.adapter) {
             vh.mAdapter = data.adapter;
-            vh.mAdapter.registerObserver(vh.mDataObserver);
+            if (vh.mAdapter != null) {
+                vh.mAdapter.registerObserver(vh.mDataObserver);
+            }
         }
         vh.mPresenter = data.presenter;
         vh.mData = data;
@@ -251,8 +254,10 @@
     @Override
     public void onUnbindViewHolder(Presenter.ViewHolder holder) {
         ViewHolder vh = (ViewHolder) holder;
-        vh.mAdapter.unregisterObserver(vh.mDataObserver);
-        vh.mAdapter = null;
+        if (vh.mAdapter != null) {
+            vh.mAdapter.unregisterObserver(vh.mDataObserver);
+            vh.mAdapter = null;
+        }
         vh.mData = null;
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
index ac14ac6..f45f20e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
@@ -20,6 +20,7 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
+import android.widget.TextView;
 
 /**
  * ControlButtonPresenterSelector displays primary and secondary
@@ -57,11 +58,13 @@
 
     static class ActionViewHolder extends Presenter.ViewHolder {
         ImageView mIcon;
+        TextView mLabel;
         View mFocusableView;
 
         public ActionViewHolder(View view) {
             super(view);
             mIcon = (ImageView) view.findViewById(R.id.icon);
+            mLabel = (TextView) view.findViewById(R.id.label);
             mFocusableView = view.findViewById(R.id.button);
         }
     }
@@ -85,7 +88,14 @@
             Action action = (Action) item;
             ActionViewHolder vh = (ActionViewHolder) viewHolder;
             vh.mIcon.setImageDrawable(action.getIcon());
-            CharSequence contentDescription = !TextUtils.isEmpty(action.getLabel1()) ?
+            if (vh.mLabel != null) {
+                if (action.getIcon() == null) {
+                    vh.mLabel.setText(action.getLabel1());
+                } else {
+                    vh.mLabel.setText(null);
+                }
+            }
+            CharSequence contentDescription = TextUtils.isEmpty(action.getLabel2()) ?
                 action.getLabel1() : action.getLabel2();
             if (!TextUtils.equals(vh.mFocusableView.getContentDescription(), contentDescription)) {
                 vh.mFocusableView.setContentDescription(contentDescription);
@@ -98,6 +108,9 @@
         public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
             ActionViewHolder vh = (ActionViewHolder) viewHolder;
             vh.mIcon.setImageDrawable(null);
+            if (vh.mLabel != null) {
+                vh.mLabel.setText(null);
+            }
             vh.mFocusableView.setContentDescription(null);
         }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
index 60fe6be..6a59d00 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
@@ -18,21 +18,61 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
  * An overview row for a details fragment. This row consists of an image, a
  * description view, and optionally a series of {@link Action}s that can be taken for
  * the item.
+ *
+ * <h3>Actions</h3>
+ * Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview
+ * row.  {@link SparseArrayObjectAdapter} is recommended for easy updating actions while
+ * keeping the order.  Application can add or remove actions on UI thread after the row is
+ * bound to view.
+ *
+ * <h3>Updating main item</h3>
+ * After the row is bound to view, application still can call ({@link #setItem(Object)})
+ * on UI thread.
+ *
+ * <h3>Updating image</h3>
+ * After the row is bound to view, application still can change image by calling ({@link
+ * #setImageBitmap(Context, Bitmap)}) or {@link #setImageDrawable(Drawable)}) on UI thread.
  */
 public class DetailsOverviewRow extends Row {
 
+    /**
+     * Listener for changes of DetailsOverViewRow.
+     */
+    static class Listener {
+
+        /**
+         * Called when DetailsOverviewRow has changed image drawable.
+         */
+        public void onImageDrawableChanged(DetailsOverviewRow row) {
+        }
+
+        /**
+         * Called when DetailsOverviewRow has changed main item.
+         */
+        public void onItemChanged(DetailsOverviewRow row) {
+        }
+
+        /**
+         * Called when DetailsOverviewRow has changed actions adapter.
+         */
+        public void onActionsAdapterChanged(DetailsOverviewRow row) {
+        }
+    }
+
     private Object mItem;
     private Drawable mImageDrawable;
-    private ArrayList<Action> mActions = new ArrayList<Action>();
     private boolean mImageScaleUpAllowed = true;
+    private ArrayList<WeakReference<Listener>> mListeners;
+    private PresenterSelector mDefaultActionPresenter = new ActionPresenterSelector();
+    private ObjectAdapter mActionsAdapter = new ArrayObjectAdapter(mDefaultActionPresenter);
 
     /**
      * Constructor for a DetailsOverviewRow.
@@ -46,6 +86,99 @@
     }
 
     /**
+     * Adds listener for the details page.
+     */
+    final void addListener(Listener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<WeakReference<Listener>>();
+        } else {
+            for (int i = 0; i < mListeners.size();) {
+                Listener l = mListeners.get(i).get();
+                if (l == null) {
+                    mListeners.remove(i);
+                } else {
+                    if (l == listener) {
+                        return;
+                    }
+                    i++;
+                }
+            }
+        }
+        mListeners.add(new WeakReference<Listener>(listener));
+    }
+
+    /**
+     * Removes listener of the details page.
+     */
+    final void removeListener(Listener listener) {
+        if (mListeners != null) {
+            for (int i = 0; i < mListeners.size();) {
+                Listener l = mListeners.get(i).get();
+                if (l == null) {
+                    mListeners.remove(i);
+                } else {
+                    if (l == listener) {
+                        mListeners.remove(i);
+                        return;
+                    }
+                    i++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners for main item change on UI thread.
+     */
+    final void notifyItemChanged() {
+        if (mListeners != null) {
+            for (int i = 0; i < mListeners.size();) {
+                Listener l = mListeners.get(i).get();
+                if (l == null) {
+                    mListeners.remove(i);
+                } else {
+                    l.onItemChanged(this);
+                    i++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners for image related change on UI thread.
+     */
+    final void notifyImageDrawableChanged() {
+        if (mListeners != null) {
+            for (int i = 0; i < mListeners.size();) {
+                Listener l = mListeners.get(i).get();
+                if (l == null) {
+                    mListeners.remove(i);
+                } else {
+                    l.onImageDrawableChanged(this);
+                    i++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners for actions adapter changed on UI thread.
+     */
+    final void notifyActionsAdapterChanged() {
+        if (mListeners != null) {
+            for (int i = 0; i < mListeners.size();) {
+                Listener l = mListeners.get(i).get();
+                if (l == null) {
+                    mListeners.remove(i);
+                } else {
+                    l.onActionsAdapterChanged(this);
+                    i++;
+                }
+            }
+        }
+    }
+
+    /**
      * Gets the main item for the details page.
      */
     public final Object getItem() {
@@ -53,22 +186,39 @@
     }
 
     /**
-     * Sets a drawable as the image of this details overview.
+     * Sets the main item for the details page.  Must be called on UI thread after
+     * row is bound to view.
+     */
+    public final void setItem(Object item) {
+        if (item != mItem) {
+            mItem = item;
+            notifyItemChanged();
+        }
+    }
+
+    /**
+     * Sets a drawable as the image of this details overview.  Must be called on UI thread
+     * after row is bound to view.
      *
      * @param drawable The drawable to set.
      */
     public final void setImageDrawable(Drawable drawable) {
-        mImageDrawable = drawable;
+        if (mImageDrawable != drawable) {
+            mImageDrawable = drawable;
+            notifyImageDrawableChanged();
+        }
     }
 
     /**
-     * Sets a Bitmap as the image of this details overview.
+     * Sets a Bitmap as the image of this details overview.  Must be called on UI thread
+     * after row is bound to view.
      *
      * @param context The context to retrieve display metrics from.
      * @param bm The bitmap to set.
      */
     public final void setImageBitmap(Context context, Bitmap bm) {
         mImageDrawable = new BitmapDrawable(context.getResources(), bm);
+        notifyImageDrawableChanged();
     }
 
     /**
@@ -83,10 +233,14 @@
 
     /**
      * Allows or disallows scaling up of images.
-     * Images will always be scaled down if necessary.
+     * Images will always be scaled down if necessary.  Must be called on UI thread
+     * after row is bound to view.
      */
     public void setImageScaleUpAllowed(boolean allowed) {
-        mImageScaleUpAllowed = allowed;
+        if (allowed != mImageScaleUpAllowed) {
+            mImageScaleUpAllowed = allowed;
+            notifyImageDrawableChanged();
+        }
     }
 
     /**
@@ -97,41 +251,80 @@
     }
 
     /**
-     * Add an Action to the overview.
-     *
-     * @param action The Action to add.
+     * Get array object adapter.  Throws ClassCastException if the current ObjectAdapter is not
+     * ArrayObjectAdapter.
      */
-    public final void addAction(Action action) {
-        mActions.add(action);
+    private ArrayObjectAdapter getArrayObjectAdapter() {
+        return (ArrayObjectAdapter) mActionsAdapter;
     }
 
     /**
-     * Add an Action to the overview at the specified position.
+     * Add an Action to the overview. It will throw ClassCastException if current actions adapter
+     * is not {@link ArrayObjectAdapter}. Must be called on UI thread.
+     *
+     * @param action The Action to add.
+     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
+     */
+    public final void addAction(Action action) {
+        getArrayObjectAdapter().add(action);
+    }
+
+    /**
+     * Add an Action to the overview at the specified position. It will throw ClassCastException if
+     * current actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
      *
      * @param pos The position to insert the Action.
      * @param action The Action to add.
+     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
      */
     public final void addAction(int pos, Action action) {
-        mActions.add(pos, action);
+        getArrayObjectAdapter().add(pos, action);
     }
 
     /**
-     * Remove the given Action from the overview.
+     * Remove the given Action from the overview. It will throw ClassCastException if current
+     * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread.
      *
      * @param action The Action to remove.
      * @return true if the overview contained the specified Action.
+     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
      */
     public final boolean removeAction(Action action) {
-        return mActions.remove(action);
+        return getArrayObjectAdapter().remove(action);
     }
 
     /**
-     * Gets a read-only view of the list of Actions of this details overview.
+     * Gets a read-only view of the list of Actions of this details overview. It will throw
+     * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be
+     * called on UI thread.
      *
      * @return An unmodifiable view of the list of Actions.
+     * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
      */
     public final List<Action> getActions() {
-        return Collections.unmodifiableList(mActions);
+        return getArrayObjectAdapter().unmodifiableList();
+    }
+
+    /**
+     * Gets {@link ObjectAdapter} for actions.
+     */
+    public final ObjectAdapter getActionsAdapter() {
+        return mActionsAdapter;
+    }
+
+    /**
+     * Sets {@link ObjectAdapter} for actions.
+     * @param adapter  Adapter for actions, a default {@link PresenterSelector} will be attached
+     *                 to the adapter if it doesn't have one.
+     */
+    public final void setActionsAdapter(ObjectAdapter adapter) {
+        if (adapter != mActionsAdapter) {
+            mActionsAdapter = adapter;
+            if (mActionsAdapter.getPresenterSelector() == null) {
+                mActionsAdapter.setPresenterSelector(mDefaultActionPresenter);
+            }
+            notifyActionsAdapterChanged();
+        }
     }
 
     private void verify() {
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 7bf2faf..8e33fb7 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
 import android.support.v17.leanback.R;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -65,6 +66,53 @@
     private static final int MORE_ACTIONS_FADE_MS = 100;
     private static final long DEFAULT_TIMEOUT = 5000;
 
+    class ActionsItemBridgeAdapter extends ItemBridgeAdapter {
+        DetailsOverviewRowPresenter.ViewHolder mViewHolder;
+
+        ActionsItemBridgeAdapter(DetailsOverviewRowPresenter.ViewHolder viewHolder) {
+            mViewHolder = viewHolder;
+        }
+
+        @Override
+        public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) {
+            if (getOnItemViewClickedListener() != null || getOnItemClickedListener() != null
+                    || mActionClickedListener != null) {
+                ibvh.getPresenter().setOnClickListener(
+                        ibvh.getViewHolder(), new View.OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                if (getOnItemViewClickedListener() != null) {
+                                    getOnItemViewClickedListener().onItemClicked(
+                                            ibvh.getViewHolder(), ibvh.getItem(),
+                                            mViewHolder, mViewHolder.getRow());
+                                }
+                                if (mActionClickedListener != null) {
+                                    mActionClickedListener.onActionClicked((Action) ibvh.getItem());
+                                }
+                            }
+                        });
+            }
+        }
+        @Override
+        public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) {
+            if (getOnItemViewClickedListener() != null || getOnItemClickedListener() != null
+                    || mActionClickedListener != null) {
+                ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null);
+            }
+        }
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+            // Remove first to ensure we don't add ourselves more than once.
+            viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
+            viewHolder.itemView.addOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
+        }
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+            viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener);
+            mViewHolder.checkFirstAndLastPosition(false);
+        }
+    }
+
     /**
      * A ViewHolder for the DetailsOverviewRow.
      */
@@ -79,9 +127,38 @@
         int mNumItems;
         boolean mShowMoreRight;
         boolean mShowMoreLeft;
-        final ItemBridgeAdapter mActionBridgeAdapter = new ItemBridgeAdapter();
+        ItemBridgeAdapter mActionBridgeAdapter;
+        final Handler mHandler = new Handler();
 
-        void bind(ObjectAdapter adapter) {
+        final Runnable mUpdateDrawableCallback = new Runnable() {
+            @Override
+            public void run() {
+                bindImageDrawable(ViewHolder.this);
+            }
+        };
+
+        final DetailsOverviewRow.Listener mListener = new DetailsOverviewRow.Listener() {
+            @Override
+            public void onImageDrawableChanged(DetailsOverviewRow row) {
+                mHandler.removeCallbacks(mUpdateDrawableCallback);
+                mHandler.post(mUpdateDrawableCallback);
+            }
+
+            @Override
+            public void onItemChanged(DetailsOverviewRow row) {
+                if (mDetailsDescriptionViewHolder != null) {
+                    mDetailsPresenter.onUnbindViewHolder(mDetailsDescriptionViewHolder);
+                }
+                mDetailsPresenter.onBindViewHolder(mDetailsDescriptionViewHolder, row.getItem());
+            }
+
+            @Override
+            public void onActionsAdapterChanged(DetailsOverviewRow row) {
+                bindActions(row.getActionsAdapter());
+            }
+        };
+
+        void bindActions(ObjectAdapter adapter) {
             mActionBridgeAdapter.setAdapter(adapter);
             mActionsRow.setAdapter(mActionBridgeAdapter);
             mNumItems = mActionBridgeAdapter.getItemCount();
@@ -135,48 +212,6 @@
             }
         };
 
-        final ItemBridgeAdapter.AdapterListener mAdapterListener =
-                new ItemBridgeAdapter.AdapterListener() {
-
-            @Override
-            public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) {
-                if (getOnItemViewClickedListener() != null || getOnItemClickedListener() != null
-                        || mActionClickedListener != null) {
-                    ibvh.getPresenter().setOnClickListener(
-                            ibvh.getViewHolder(), new View.OnClickListener() {
-                                @Override
-                                public void onClick(View v) {
-                                    if (getOnItemViewClickedListener() != null) {
-                                        getOnItemViewClickedListener().onItemClicked(ibvh.getViewHolder(),
-                                                ibvh.getItem(), ViewHolder.this, getRow());
-                                    }
-                                    if (mActionClickedListener != null) {
-                                        mActionClickedListener.onActionClicked((Action) ibvh.getItem());
-                                    }
-                                }
-                            });
-                }
-            }
-            @Override
-            public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) {
-                if (getOnItemViewClickedListener() != null || getOnItemClickedListener() != null
-                        || mActionClickedListener != null) {
-                    ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null);
-                }
-            }
-            @Override
-            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
-                // Remove first to ensure we don't add ourselves more than once.
-                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
-                viewHolder.itemView.addOnLayoutChangeListener(mLayoutChangeListener);
-            }
-            @Override
-            public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
-                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
-                checkFirstAndLastPosition(false);
-            }
-        };
-
         final RecyclerView.OnScrollListener mScrollListener =
                 new RecyclerView.OnScrollListener() {
 
@@ -252,13 +287,10 @@
             mDetailsDescriptionViewHolder =
                     detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame);
             mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view);
-
-            mActionBridgeAdapter.setAdapterListener(mAdapterListener);
         }
     }
 
     private final Presenter mDetailsPresenter;
-    private final ActionPresenterSelector mActionPresenterSelector;
     private OnActionClickedListener mActionClickedListener;
 
     private int mBackgroundColor = Color.TRANSPARENT;
@@ -277,7 +309,6 @@
         setHeaderPresenter(null);
         setSelectEffectEnabled(false);
         mDetailsPresenter = detailsPresenter;
-        mActionPresenterSelector = new ActionPresenterSelector();
     }
 
     /**
@@ -369,6 +400,7 @@
         return context.getResources().getColor(outValue.resourceId);
     }
 
+    @Override
     protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
         super.onRowViewSelected(vh, selected);
         if (selected) {
@@ -394,6 +426,7 @@
     }
 
     private void initDetailsOverview(ViewHolder vh) {
+        vh.mActionBridgeAdapter = new ActionsItemBridgeAdapter(vh);
         final View overview = vh.mOverviewFrame;
         ViewGroup.LayoutParams lp = overview.getLayoutParams();
         lp.height = getCardHeight(overview.getContext());
@@ -414,12 +447,8 @@
         return (height > 0 ? height : 0);
     }
 
-    @Override
-    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
-        super.onBindRowViewHolder(holder, item);
-
-        DetailsOverviewRow row = (DetailsOverviewRow) item;
-        ViewHolder vh = (ViewHolder) holder;
+    private void bindImageDrawable(ViewHolder vh) {
+        DetailsOverviewRow row = (DetailsOverviewRow) vh.getRow();
 
         ViewGroup.MarginLayoutParams layoutParams =
                 (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams();
@@ -467,7 +496,7 @@
             getDefaultBackgroundColor(vh.mOverviewView.getContext());
 
         if (useMargin) {
-            layoutParams.leftMargin = horizontalMargin;
+            layoutParams.setMarginStart(horizontalMargin);
             layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
             RoundedRectHelper.getInstance().setRoundedRectBackground(vh.mOverviewFrame, bgColor);
             vh.mRightPanel.setBackground(null);
@@ -494,26 +523,33 @@
         }
         vh.mImageView.setLayoutParams(layoutParams);
         vh.mImageView.setImageDrawable(row.getImageDrawable());
-
-        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
-
-        ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
-        aoa.addAll(0, (Collection)row.getActions());
-        vh.bind(aoa);
-
         if (row.getImageDrawable() != null && mSharedElementHelper != null) {
             mSharedElementHelper.onBindToDrawable(vh);
         }
     }
 
     @Override
-    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
-        super.onUnbindRowViewHolder(holder);
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
 
+        DetailsOverviewRow row = (DetailsOverviewRow) item;
         ViewHolder vh = (ViewHolder) holder;
+
+        bindImageDrawable(vh);
+        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
+        vh.bindActions(row.getActionsAdapter());
+        row.addListener(vh.mListener);
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        ViewHolder vh = (ViewHolder) holder;
+        DetailsOverviewRow dor = (DetailsOverviewRow) vh.getRow();
+        dor.removeListener(vh.mListener);
         if (vh.mDetailsDescriptionViewHolder != null) {
             mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
         }
+        super.onUnbindRowViewHolder(holder);
     }
 
     @Override
@@ -530,4 +566,22 @@
             ((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor);
         }
     }
+
+    @Override
+    protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
+        super.onRowViewAttachedToWindow(vh);
+        if (mDetailsPresenter != null) {
+            mDetailsPresenter.onViewAttachedToWindow(
+                    ((ViewHolder) vh).mDetailsDescriptionViewHolder);
+        }
+    }
+
+    @Override
+    protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
+        super.onRowViewDetachedFromWindow(vh);
+        if (mDetailsPresenter != null) {
+            mDetailsPresenter.onViewDetachedFromWindow(
+                    ((ViewHolder) vh).mDetailsDescriptionViewHolder);
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
index 6052c81b..410aa28 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
@@ -22,24 +22,93 @@
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
 import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Matrix;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.MeasureSpec;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
 
 import java.util.List;
 
 final class DetailsOverviewSharedElementHelper extends SharedElementCallback {
 
+    private static final String TAG = "DetailsOverviewSharedElementHelper";
+    private static final boolean DEBUG = false;
+
     private ViewHolder mViewHolder;
     private Activity mActivityToRunTransition;
+    private boolean mStartedPostpone;
     private String mSharedElementName;
     private int mRightPanelWidth;
     private int mRightPanelHeight;
 
+    private ScaleType mSavedScaleType;
+    private Matrix mSavedMatrix;
+
+    private boolean hasImageViewScaleChange(View snapshotView) {
+        return snapshotView instanceof ImageView;
+    }
+
+    private void saveImageViewScale() {
+        if (mSavedScaleType == null) {
+            // only save first time after initialize/restoreImageViewScale()
+            ImageView imageView = mViewHolder.mImageView;
+            mSavedScaleType = imageView.getScaleType();
+            mSavedMatrix = mSavedScaleType == ScaleType.MATRIX ? imageView.getMatrix() : null;
+            if (DEBUG) {
+                Log.d(TAG, "saveImageViewScale: "+mSavedScaleType);
+            }
+        }
+    }
+
+    private static void updateImageViewAfterScaleTypeChange(ImageView imageView) {
+        // enforcing imageView to update its internal bounds/matrix immediately
+        imageView.measure(
+                MeasureSpec.makeMeasureSpec(imageView.getMeasuredWidth(), MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(imageView.getMeasuredHeight(), MeasureSpec.EXACTLY));
+        imageView.layout(imageView.getLeft(), imageView.getTop(),
+                imageView.getRight(), imageView.getBottom());
+    }
+
+    private void changeImageViewScale(View snapshotView) {
+        ImageView snapshotImageView = (ImageView) snapshotView;
+        ImageView imageView = mViewHolder.mImageView;
+        if (DEBUG) {
+            Log.d(TAG, "changeImageViewScale to "+snapshotImageView.getScaleType());
+        }
+        imageView.setScaleType(snapshotImageView.getScaleType());
+        if (snapshotImageView.getScaleType() == ScaleType.MATRIX) {
+            imageView.setImageMatrix(snapshotImageView.getImageMatrix());
+        }
+        updateImageViewAfterScaleTypeChange(imageView);
+    }
+
+    private void restoreImageViewScale() {
+        if (mSavedScaleType != null) {
+            if (DEBUG) {
+                Log.d(TAG, "restoreImageViewScale to "+mSavedScaleType);
+            }
+            ImageView imageView = mViewHolder.mImageView;
+            imageView.setScaleType(mSavedScaleType);
+            if (mSavedScaleType == ScaleType.MATRIX) {
+                imageView.setImageMatrix(mSavedMatrix);
+            }
+            // only restore once unless another save happens
+            mSavedScaleType = null;
+            updateImageViewAfterScaleTypeChange(imageView);
+        }
+    }
+
     @Override
     public void onSharedElementStart(List<String> sharedElementNames,
             List<View> sharedElements, List<View> sharedElementSnapshots) {
+        if (DEBUG) {
+            Log.d(TAG, "onSharedElementStart " + mActivityToRunTransition);
+        }
         if (sharedElements.size() < 1) {
             return;
         }
@@ -47,6 +116,11 @@
         if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
             return;
         }
+        View snapshot = sharedElementSnapshots.get(0);
+        if (hasImageViewScaleChange(snapshot)) {
+            saveImageViewScale();
+            changeImageViewScale(snapshot);
+        }
         View imageView = mViewHolder.mImageView;
         final int width = overviewView.getWidth();
         final int height = overviewView.getHeight();
@@ -69,6 +143,9 @@
     @Override
     public void onSharedElementEnd(List<String> sharedElementNames,
             List<View> sharedElements, List<View> sharedElementSnapshots) {
+        if (DEBUG) {
+            Log.d(TAG, "onSharedElementEnd " + mActivityToRunTransition);
+        }
         if (sharedElements.size() < 1) {
             return;
         }
@@ -76,6 +153,7 @@
         if (mViewHolder == null || mViewHolder.mOverviewFrame != overviewView) {
             return;
         }
+        restoreImageViewScale();
         // temporary let action row take focus so we defer button background animation
         mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
@@ -98,25 +176,38 @@
         }
         mActivityToRunTransition = activity;
         mSharedElementName = sharedElementName;
-        if (mActivityToRunTransition != null) {
-            ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
-            ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
-            if (timeoutMs > 0) {
-                new Handler().postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mActivityToRunTransition == null) {
-                            return;
-                        }
-                        ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
-                        mActivityToRunTransition = null;
+        if (DEBUG) {
+            Log.d(TAG, "postponeEnterTransition " + mActivityToRunTransition);
+        }
+        ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this);
+        ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
+        if (timeoutMs > 0) {
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    if (mStartedPostpone) {
+                        return;
                     }
-                }, timeoutMs);
-            }
+                    if (DEBUG) {
+                        Log.d(TAG, "timeout " + mActivityToRunTransition);
+                    }
+                    startPostponedEnterTransition();
+                }
+            }, timeoutMs);
         }
     }
 
     void onBindToDrawable(ViewHolder vh) {
+        if (DEBUG) {
+            Log.d(TAG, "onBindToDrawable, could start transition of " + mActivityToRunTransition);
+        }
+        if (mViewHolder != null) {
+            if (DEBUG) {
+                Log.d(TAG, "rebind? clear transitionName on current viewHolder "
+                        + mViewHolder.mOverviewFrame);
+            }
+            ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, null);
+        }
         // After we got a image drawable,  we can determine size of right panel.
         // We want right panel to have fixed size so that the right panel don't change size
         // when the overview is layout as a small bounds in transition.
@@ -128,37 +219,50 @@
                 mViewHolder.mRightPanel.removeOnLayoutChangeListener(this);
                 mRightPanelWidth = mViewHolder.mRightPanel.getWidth();
                 mRightPanelHeight = mViewHolder.mRightPanel.getHeight();
+                if (DEBUG) {
+                    Log.d(TAG, "onLayoutChange records size of right panel as "
+                            + mRightPanelWidth + ", "+ mRightPanelHeight);
+                }
             }
         });
-        if (mActivityToRunTransition != null) {
-            mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
-                @Override
-                public void run() {
-                    if (mActivityToRunTransition == null) {
-                        return;
-                    }
-                    final TransitionHelper transitionHelper = TransitionHelper.getInstance();
-                    Object transition = transitionHelper.getSharedElementEnterTransition(
-                            mActivityToRunTransition.getWindow());
-                    if (transition != null) {
-                        transitionHelper.setTransitionListener(transition, new TransitionListener() {
-                            @Override
-                            public void onTransitionEnd(Object transition) {
-                                // after transition if the action row still focused, transfer
-                                // focus to its children
-                                if (mViewHolder.mActionsRow.isFocused()) {
-                                    mViewHolder.mActionsRow.requestFocus();
-                                }
-                                transitionHelper.setTransitionListener(transition, null);
-                            }
-                        });
-                    }
-                    ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
-                    ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
-                    mActivityToRunTransition = null;
-                    mSharedElementName = null;
+        mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
+            @Override
+            public void run() {
+                if (DEBUG) {
+                    Log.d(TAG, "setTransitionName "+mViewHolder.mOverviewFrame);
                 }
-            });
+                ViewCompat.setTransitionName(mViewHolder.mOverviewFrame, mSharedElementName);
+                final TransitionHelper transitionHelper = TransitionHelper.getInstance();
+                Object transition = transitionHelper.getSharedElementEnterTransition(
+                        mActivityToRunTransition.getWindow());
+                if (transition != null) {
+                    transitionHelper.setTransitionListener(transition, new TransitionListener() {
+                        @Override
+                        public void onTransitionEnd(Object transition) {
+                            if (DEBUG) {
+                                Log.d(TAG, "onTransitionEnd " + mActivityToRunTransition);
+                            }
+                            // after transition if the action row still focused, transfer
+                            // focus to its children
+                            if (mViewHolder.mActionsRow.isFocused()) {
+                                mViewHolder.mActionsRow.requestFocus();
+                            }
+                            transitionHelper.setTransitionListener(transition, null);
+                        }
+                    });
+                }
+                startPostponedEnterTransition();
+            }
+        });
+    }
+
+    private void startPostponedEnterTransition() {
+        if (!mStartedPostpone) {
+            if (DEBUG) {
+                Log.d(TAG, "startPostponedEnterTransition " + mActivityToRunTransition);
+            }
+            ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
+            mStartedPostpone = true;
         }
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
index 834b7bf..5b1ad6c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
@@ -38,4 +38,8 @@
      */
     public static final int ZOOM_FACTOR_LARGE = 3;
 
+    /**
+     * An extra small zoom factor.
+     */
+    public static final int ZOOM_FACTOR_XSMALL = 4;
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index f6b633e..1a4ea48 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -22,6 +22,7 @@
 import android.content.res.Resources;
 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_NONE;
 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_SMALL;
+import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_XSMALL;
 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_MEDIUM;
 import static android.support.v17.leanback.widget.FocusHighlight.ZOOM_FACTOR_LARGE;
 
@@ -30,6 +31,26 @@
  */
 public class FocusHighlightHelper {
 
+    static boolean isValidZoomIndex(int zoomIndex) {
+        return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
+    }
+
+    private static int getResId(int zoomIndex) {
+        switch (zoomIndex) {
+            case ZOOM_FACTOR_SMALL:
+                return R.fraction.lb_focus_zoom_factor_small;
+            case ZOOM_FACTOR_XSMALL:
+                return R.fraction.lb_focus_zoom_factor_xsmall;
+            case ZOOM_FACTOR_MEDIUM:
+                return R.fraction.lb_focus_zoom_factor_medium;
+            case ZOOM_FACTOR_LARGE:
+                return R.fraction.lb_focus_zoom_factor_large;
+            default:
+                return 0;
+        }
+    }
+
+
     static class FocusAnimator implements TimeAnimator.TimeListener {
         private final View mView;
         private final int mDuration;
@@ -112,32 +133,20 @@
     static class BrowseItemFocusHighlight implements FocusHighlightHandler {
         private static final int DURATION_MS = 150;
 
-        private static float[] sScaleFactor = new float[4];
-
         private int mScaleIndex;
         private final boolean mUseDimmer;
 
         BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
-            mScaleIndex = (zoomIndex >= 0 && zoomIndex < sScaleFactor.length) ?
-                    zoomIndex : ZOOM_FACTOR_MEDIUM;
+            if (!isValidZoomIndex(zoomIndex)) {
+                throw new IllegalArgumentException("Unhandled zoom index");
+            }
+            mScaleIndex = zoomIndex;
             mUseDimmer = useDimmer;
         }
 
-        private static void lazyInit(Resources resources) {
-            if (sScaleFactor[ZOOM_FACTOR_NONE] == 0f) {
-                sScaleFactor[ZOOM_FACTOR_NONE] = 1f;
-                sScaleFactor[ZOOM_FACTOR_SMALL] =
-                        resources.getFraction(R.fraction.lb_focus_zoom_factor_small, 1, 1);
-                sScaleFactor[ZOOM_FACTOR_MEDIUM] =
-                        resources.getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1);
-                sScaleFactor[ZOOM_FACTOR_LARGE] =
-                        resources.getFraction(R.fraction.lb_focus_zoom_factor_large, 1, 1);
-            }
-        }
-
-        private float getScale(View view) {
-            lazyInit(view.getResources());
-            return sScaleFactor[mScaleIndex];
+        private float getScale(Resources res) {
+            return mScaleIndex == ZOOM_FACTOR_NONE ? 1f :
+                    res.getFraction(getResId(mScaleIndex), 1, 1);
         }
 
         @Override
@@ -154,7 +163,8 @@
         private FocusAnimator getOrCreateAnimator(View view) {
             FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
             if (animator == null) {
-                animator = new FocusAnimator(view, getScale(view), mUseDimmer, DURATION_MS);
+                animator = new FocusAnimator(
+                        view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
                 view.setTag(R.id.lb_focus_animator, animator);
             }
             return animator;
@@ -164,8 +174,11 @@
 
     /**
      * Setup the focus highlight behavior of a focused item in browse list row.
-     * @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL} {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
-     * {@link FocusHighlight#ZOOM_FACTOR_LARGE} {@link FocusHighlight#ZOOM_FACTOR_NONE}.
+     * @param zoomIndex One of {@link FocusHighlight#ZOOM_FACTOR_SMALL}
+     * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}
+     * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}
+     * {@link FocusHighlight#ZOOM_FACTOR_LARGE}
+     * {@link FocusHighlight#ZOOM_FACTOR_NONE}.
      * @param useDimmer Allow dimming browse item when unselected.
      * @param adapter  adapter of the list row.
      */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
new file mode 100644
index 0000000..5ebc4a6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v4.util.CircularIntArray;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+/**
+ * A grid is representation of multiple row layout data structure and algorithm.
+ * Grid is the base class for both staggered case or simple non-staggered case.
+ * <p>
+ * User calls Grid.createStaggeredMutipleRows() to create an staggered instance.
+ * TODO add createNonStaggeredRows().
+ * To use the Grid, user must implement a Provider to create or remove visible item.
+ * Grid maintains a list of visible items.  Visible items are created when
+ * user calls appendVisibleItems() or prependVisibleItems() with certain limitation
+ * (e.g. a max edge that append up to).  Visible items are deleted when user calls
+ * removeInvisibleItemsAtEnd() or removeInvisibleItemsAtFront().  Grid's algorithm
+ * uses size of visible item returned from Provider.createItem() to decide which row
+ * to add a new visible item and may cache the algorithm results.   User must call
+ * invalidateItemsAfter() when it detects item size changed to ask Grid to remove cached
+ * results.
+ */
+abstract class Grid {
+
+    /**
+     * A constant representing a default starting index, indicating that the
+     * developer did not provide a start index.
+     */
+    public static final int START_DEFAULT = -1;
+
+    /**
+     * When user uses Grid,  he should provide count of items and
+     * the method to create item and remove item.
+     */
+    public static interface Provider {
+
+        /**
+         * Return how many items (are in the adapter).
+         */
+        public abstract int getCount();
+
+        /**
+         * Create visible item and where the provider should measure it.
+         * The call is always followed by addItem().
+         * @param index     0-based index of the item in provider
+         * @param append  True if new item is after last visible item, false if new item is
+         *                before first visible item.
+         * @param item    item[0] returns created item that will be passed in addItem() call.
+         * @return length of the item.
+         */
+        public abstract int createItem(int index, boolean append, Object[] item);
+
+        /**
+         * add item to given row and given edge.  The call is always after createItem().
+         * @param item      The object returned by createItem()
+         * @param index     0-based index of the item in provider
+         * @param length    The size of the object
+         * @param rowIndex  Row index to put the item
+         * @param edge      min_edge if not reversed or max_edge if reversed.
+         */
+        public abstract void addItem(Object item, int index, int length, int rowIndex, int edge);
+
+        /**
+         * Remove visible item at index.
+         * @param index     0-based index of the item in provider
+         */
+        public abstract void removeItem(int index);
+
+        /**
+         * Get edge of an existing visible item. edge will be the min_edge
+         * if not reversed or the max_edge if reversed.
+         * @param index     0-based index of the item in provider
+         */
+        public abstract int getEdge(int index);
+
+        /**
+         * Get size of an existing visible item.
+         * @param index     0-based index of the item in provider
+         */
+        public abstract int getSize(int index);
+    }
+
+    /**
+     * Cached representation of an item in Grid.  May be subclassed.
+     */
+    public static class Location {
+        /**
+         * The index of the row for this Location.
+         */
+        public int row;
+
+        public Location(int row) {
+            this.row = row;
+        }
+    }
+
+    protected Provider mProvider;
+    protected boolean mReversedFlow;
+    protected int mMargin;
+    protected int mNumRows;
+    protected int mFirstVisibleIndex = -1;
+    protected int mLastVisibleIndex = -1;
+
+    protected CircularIntArray[] mTmpItemPositionsInRows;
+
+    // the first index that grid will layout
+    protected int mStartIndex = START_DEFAULT;
+
+    /**
+     * Creates a multiple rows staggered grid.
+     */
+    public static Grid createStaggeredMultipleRows(int rows) {
+        StaggeredGridDefault grid = new StaggeredGridDefault();
+        grid.setNumRows(rows);
+        return grid;
+    }
+
+    /**
+     * Sets the margin between items in a row
+     */
+    public final void setMargin(int margin) {
+        mMargin = margin;
+    }
+
+    /**
+     * Sets if reversed flow (rtl)
+     */
+    public final void setReversedFlow(boolean reversedFlow) {
+        mReversedFlow = reversedFlow;
+    }
+
+    /**
+     * Returns true if reversed flow (rtl)
+     */
+    public boolean isReversedFlow() {
+        return mReversedFlow;
+    }
+
+    /**
+     * Sets the {@link Provider} for this grid.
+     *
+     * @param provider The provider for this grid.
+     */
+    public void setProvider(Provider provider) {
+        mProvider = provider;
+    }
+
+    /**
+     * Sets the first item index to create when there are no items.
+     *
+     * @param startIndex the index of the first item
+     */
+    public void setStart(int startIndex) {
+        mStartIndex = startIndex;
+    }
+
+    /**
+     * Returns the number of rows in the grid.
+     */
+    public int getNumRows() {
+        return mNumRows;
+    }
+
+    /**
+     * Sets number of rows to fill into. For views that represent a
+     * horizontal list, this will be the rows of the view. For views that
+     * represent a vertical list, this will be the columns.
+     *
+     * @param numRows numberOfRows
+     */
+    void setNumRows(int numRows) {
+        if (numRows <= 0) {
+            throw new IllegalArgumentException();
+        }
+        if (mNumRows == numRows) {
+            return;
+        }
+        mNumRows = numRows;
+        mTmpItemPositionsInRows = new CircularIntArray[mNumRows];
+        for (int i = 0; i < mNumRows; i++) {
+            mTmpItemPositionsInRows[i] = new CircularIntArray();
+        }
+    }
+
+    /**
+     * Returns index of first visible item in the staggered grid.  Returns negative value
+     * if no visible item.
+     */
+    public final int getFirstVisibleIndex() {
+        return mFirstVisibleIndex;
+    }
+
+    /**
+     * Returns index of last visible item in the staggered grid.  Returns negative value
+     * if no visible item.
+     */
+    public final int getLastVisibleIndex() {
+        return mLastVisibleIndex;
+    }
+
+    /**
+     * Reset visible indices and keep cache (if exists)
+     */
+    public void resetVisibleIndex() {
+        mFirstVisibleIndex = mLastVisibleIndex = -1;
+    }
+
+    /**
+     * Invalidate items after or equal to index. This will remove visible items
+     * after that and invalidate cache of layout results after that.
+     */
+    public void invalidateItemsAfter(int index) {
+        if (index < 0) {
+            return;
+        }
+        if (mLastVisibleIndex < 0) {
+            return;
+        }
+        while (mLastVisibleIndex >= index) {
+            mProvider.removeItem(mLastVisibleIndex);
+            mLastVisibleIndex--;
+        }
+        resetVisbileIndexIfEmpty();
+        if (getFirstVisibleIndex() < 0) {
+            setStart(index);
+        }
+    }
+
+    /**
+     * Gets the row index of item at given index.
+     */
+    public final int getRowIndex(int index) {
+        return getLocation(index).row;
+    }
+
+    /**
+     * Gets {@link Location} of item.  The return object is read only and temporarily.
+     */
+    public abstract Location getLocation(int index);
+
+    /**
+     * Finds the largest or smallest row min edge of visible items,
+     * the row index is returned in indices[0], the item index is returned in indices[1].
+     */
+    public final int findRowMin(boolean findLarge, int[] indices) {
+        return findRowMin(findLarge, mReversedFlow ? mLastVisibleIndex : mFirstVisibleIndex,
+                indices);
+    }
+
+    /**
+     * Finds the largest or smallest row min edge of visible items, starts searching from
+     * indexLimit, the row index is returned in indices[0], the item index is returned in indices[1].
+     */
+    protected abstract int findRowMin(boolean findLarge, int indexLimit, int[] rowIndex);
+
+    /**
+     * Finds the largest or smallest row max edge of visible items, the row index is returned in
+     * indices[0], the item index is returned in indices[1].
+     */
+    public final int findRowMax(boolean findLarge, int[] indices) {
+        return findRowMax(findLarge, mReversedFlow ? mFirstVisibleIndex : mLastVisibleIndex,
+                indices);
+    }
+
+    /**
+     * Find largest or smallest row max edge of visible items, starts searching from indexLimit,
+     * the row index is returned in indices[0], the item index is returned in indices[1].
+     */
+    protected abstract int findRowMax(boolean findLarge, int indexLimit, int[] indices);
+
+    /**
+     * Returns true if appending item has reached "toLimit"
+     */
+    protected final boolean checkAppendOverLimit(int toLimit) {
+        if (mLastVisibleIndex < 0) {
+            return false;
+        }
+        return mReversedFlow ? findRowMin(true, null) <= toLimit + mMargin :
+                    findRowMax(false, null) >= toLimit - mMargin;
+    }
+
+    /**
+     * Returns true if prepending item has reached "toLimit"
+     */
+    protected final boolean checkPrependOverLimit(int toLimit) {
+        if (mLastVisibleIndex < 0) {
+            return false;
+        }
+        return mReversedFlow ? findRowMax(false, null) >= toLimit + mMargin :
+                    findRowMin(true, null) <= toLimit - mMargin;
+    }
+
+    /**
+     * Return array of int array for all rows, each int array contains visible item positions
+     * in pair on that row between startPos(included) and endPositions(included).
+     * Returned value is read only, do not change it.
+     * <p>
+     * E.g. First row has 3,7,8, second row has 4,5,6.
+     * getItemPositionsInRows(3, 8) returns { {3,3,7,8}, {4,6} }
+     */
+    public abstract CircularIntArray[] getItemPositionsInRows(int startPos, int endPos);
+
+    /**
+     * Return array of int array for all rows, each int array contains visible item positions
+     * in pair on that row.
+     * Returned value is read only, do not change it.
+     * <p>
+     * E.g. First row has 3,7,8, second row has 4,5,6  { {3,3,7,8}, {4,6} }
+     */
+    public final CircularIntArray[] getItemPositionsInRows() {
+        return getItemPositionsInRows(getFirstVisibleIndex(), getLastVisibleIndex());
+    }
+
+    /**
+     * Prepends items and stops after one column is filled.
+     * (i.e. filled items from row 0 to row mNumRows - 1)
+     * @return true if at least one item is filled.
+     */
+    public final boolean prependOneColumnVisibleItems() {
+        return prependVisibleItems(mReversedFlow ? Integer.MIN_VALUE : Integer.MAX_VALUE, true);
+    }
+
+    /**
+     * Prepends items until first item or reaches toLimit (min edge when not reversed or
+     * max edge when reversed)
+     */
+    public final void prependVisibleItems(int toLimit) {
+        prependVisibleItems(toLimit, false);
+    }
+
+    /**
+     * Prepends items until first item or reaches toLimit (min edge when not reversed or
+     * max edge when reversed).
+     * @param oneColumnMode  true when fills one column and stops,  false
+     * when checks if condition matches before filling first column.
+     * @return true if at least one item is filled.
+     */
+    protected abstract boolean prependVisibleItems(int toLimit, boolean oneColumnMode);
+
+    /**
+     * Appends items and stops after one column is filled.
+     * (i.e. filled items from row 0 to row mNumRows - 1)
+     * @return true if at least one item is filled.
+     */
+    public boolean appendOneColumnVisibleItems() {
+        return appendVisibleItems(mReversedFlow ? Integer.MAX_VALUE : Integer.MIN_VALUE, true);
+    }
+
+    /**
+     * Append items until last item or reaches toLimit (max edge when not
+     * reversed or min edge when reversed)
+     */
+    public final void appendVisibleItems(int toLimit) {
+        appendVisibleItems(toLimit, false);
+    }
+
+    /**
+     * Appends items until last or reaches toLimit (high edge when not
+     * reversed or low edge when reversed).
+     * @param oneColumnMode True when fills one column and stops,  false
+     * when checks if condition matches before filling first column.
+     * @return true if filled at least one item
+     */
+    protected abstract boolean appendVisibleItems(int toLimit, boolean oneColumnMode);
+
+    /**
+     * Removes invisible items from end until reaches item at aboveIndex or toLimit.
+     */
+    public void removeInvisibleItemsAtEnd(int aboveIndex, int toLimit) {
+        while(mLastVisibleIndex >= mFirstVisibleIndex && mLastVisibleIndex > aboveIndex) {
+            boolean offEnd = !mReversedFlow ? mProvider.getEdge(mLastVisibleIndex) >= toLimit
+                    : mProvider.getEdge(mLastVisibleIndex) <= toLimit;
+            if (offEnd) {
+                mProvider.removeItem(mLastVisibleIndex);
+                mLastVisibleIndex--;
+            } else {
+                break;
+            }
+        }
+        resetVisbileIndexIfEmpty();
+    }
+
+    /**
+     * Removes invisible items from front until reaches item at belowIndex or toLimit.
+     */
+    public void removeInvisibleItemsAtFront(int belowIndex, int toLimit) {
+        while(mLastVisibleIndex >= mFirstVisibleIndex && mFirstVisibleIndex < belowIndex) {
+            boolean offFront = !mReversedFlow ? mProvider.getEdge(mFirstVisibleIndex)
+                    + mProvider.getSize(mFirstVisibleIndex) <= toLimit
+                    : mProvider.getEdge(mFirstVisibleIndex)
+                            - mProvider.getSize(mFirstVisibleIndex) >= toLimit;
+            if (offFront) {
+                mProvider.removeItem(mFirstVisibleIndex);
+                mFirstVisibleIndex++;
+            } else {
+                break;
+            }
+        }
+        resetVisbileIndexIfEmpty();
+    }
+
+    private void resetVisbileIndexIfEmpty() {
+        if (mLastVisibleIndex < mFirstVisibleIndex) {
+            resetVisibleIndex();
+        }
+    }
+
+    public abstract void debugPrint(PrintWriter pw);
+}
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 b6c4fa0..48786cb 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -19,11 +19,13 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.util.CircularIntArray;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.widget.LinearSmoothScroller;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.Recycler;
 import android.support.v7.widget.RecyclerView.State;
+import android.support.v17.leanback.os.TraceHelper;
 
 import static android.support.v7.widget.RecyclerView.NO_ID;
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
@@ -61,7 +63,7 @@
         // For placement
         private int mLeftInset;
         private int mTopInset;
-        private int mRighInset;
+        private int mRightInset;
         private int mBottomInset;
 
         // For alignment
@@ -109,7 +111,7 @@
         }
 
         int getOpticalRight(View view) {
-            return view.getRight() - mRighInset;
+            return view.getRight() - mRightInset;
         }
 
         int getOpticalBottom(View view) {
@@ -117,7 +119,7 @@
         }
 
         int getOpticalWidth(View view) {
-            return view.getWidth() - mLeftInset - mRighInset;
+            return view.getWidth() - mLeftInset - mRightInset;
         }
 
         int getOpticalHeight(View view) {
@@ -129,7 +131,7 @@
         }
 
         int getOpticalRightInset() {
-            return mRighInset;
+            return mRightInset;
         }
 
         int getOpticalTopInset() {
@@ -151,7 +153,7 @@
         void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
             mLeftInset = leftInset;
             mTopInset = topInset;
-            mRighInset = rightInset;
+            mRightInset = rightInset;
             mBottomInset = bottomInset;
         }
 
@@ -164,8 +166,153 @@
         }
     }
 
+    /**
+     * Base class which scrolls to selected view in onStop().
+     */
+    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
+        GridLinearSmoothScroller() {
+            super(mBaseGridView.getContext());
+        }
+
+        @Override
+        protected void onStop() {
+            // onTargetFound() may not be called if we hit the "wall" first.
+            View targetView = findViewByPosition(getTargetPosition());
+            if (hasFocus() && targetView != null) {
+                targetView.requestFocus();
+            }
+            dispatchChildSelected();
+            super.onStop();
+        }
+
+        @Override
+        protected void onTargetFound(View targetView,
+                RecyclerView.State state, Action action) {
+            if (getScrollPosition(targetView, sTwoInts)) {
+                int dx, dy;
+                if (mOrientation == HORIZONTAL) {
+                    dx = sTwoInts[0];
+                    dy = sTwoInts[1];
+                } else {
+                    dx = sTwoInts[1];
+                    dy = sTwoInts[0];
+                }
+                final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+                final int time = calculateTimeForDeceleration(distance);
+                action.update(dx, dy, time, mDecelerateInterpolator);
+            }
+        }
+    }
+
+    /**
+     * The SmoothScroller that remembers pending DPAD keys and consume pending keys
+     * during scroll.
+     */
+    final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
+        // -2 is a target position that LinearSmoothScroller can never find until
+        // consumePendingMoves() sets real targetPosition.
+        final static int TARGET_UNDEFINED = -2;
+
+        // Number of pending movements on primary direction, negative if PREV_ITEM.
+        private int mPendingMoves;
+
+        PendingMoveSmoothScroller(int initialPendingMoves) {
+            mPendingMoves = initialPendingMoves;
+            setTargetPosition(TARGET_UNDEFINED);
+        }
+
+        void increasePendingMoves() {
+            if (mPendingMoves < MAX_PENDING_MOVES) {
+                mPendingMoves++;
+            }
+        }
+
+        void decreasePendingMoves() {
+            if (mPendingMoves > -MAX_PENDING_MOVES) {
+                mPendingMoves--;
+            }
+        }
+
+        void consumePendingMoves() {
+            if (mPendingMoves != 0) {
+                // consume pending moves, focus to item on the same row.
+                final int focusedRow = mGrid != null && mFocusPosition != NO_POSITION ?
+                        mGrid.getLocation(mFocusPosition).row : NO_POSITION;
+                for (int i = 0, count = getChildCount(); i < count && mPendingMoves != 0; i++) {
+                    int index = mPendingMoves > 0 ? i : count - 1 - i;
+                    final View child = getChildAt(index);
+                    if (child.getVisibility() != View.VISIBLE) {
+                        continue;
+                    }
+                    int position = getPositionByIndex(index);
+                    Grid.Location loc = mGrid.getLocation(position);
+                    if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
+                        if (mFocusPosition == NO_POSITION) {
+                            mFocusPosition = position;
+                        } else if ((mPendingMoves > 0 && position > mFocusPosition)
+                                || (mPendingMoves < 0 && position < mFocusPosition)) {
+                            mFocusPosition = position;
+                            if (mPendingMoves > 0) {
+                                mPendingMoves--;
+                            } else {
+                                mPendingMoves++;
+                            }
+                            if (hasFocus()) {
+                                View v = findViewByPosition(mFocusPosition);
+                                if (v != null) {
+                                    v.requestFocus();
+                                }
+                                dispatchChildSelected();
+                            }
+                        }
+                    }
+                }
+            }
+            if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
+                    || (mPendingMoves < 0 && hasCreatedFirstItem())) {
+                setTargetPosition(mFocusPosition);
+            }
+        }
+
+        @Override
+        protected void updateActionForInterimTarget(Action action) {
+            if (mPendingMoves == 0) {
+                return;
+            }
+            super.updateActionForInterimTarget(action);
+        }
+
+        @Override
+        public PointF computeScrollVectorForPosition(int targetPosition) {
+            if (mPendingMoves == 0) {
+                return null;
+            }
+            int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0) ?
+                    -1 : 1;
+            if (mOrientation == HORIZONTAL) {
+                return new PointF(direction, 0);
+            } else {
+                return new PointF(0, direction);
+            }
+        }
+
+        @Override
+        protected void onStop() {
+            // if we hit wall,  need clear the remaining pending moves.
+            mPendingMoves = 0;
+            mPendingMoveSmoothScroller = null;
+            super.onStop();
+            View v = findViewByPosition(getTargetPosition());
+            if (v != null) scrollToView(v, true);
+        }
+    };
+
     private static final String TAG = "GridLayoutManager";
     private static final boolean DEBUG = false;
+    private static final boolean TRACE = false;
+
+    // maximum pending movement in one direction.
+    private final static int MAX_PENDING_MOVES = 10;
 
     private String getTag() {
         return TAG + ":" + mBaseGridView.getId();
@@ -174,6 +321,30 @@
     private final BaseGridView mBaseGridView;
 
     /**
+     * Note on conventions in the presence of RTL layout directions:
+     * Many properties and method names reference entities related to the
+     * beginnings and ends of things.  In the presence of RTL flows,
+     * it may not be clear whether this is intended to reference a
+     * quantity that changes direction in RTL cases, or a quantity that
+     * does not.  Here are the conventions in use:
+     *
+     * start/end: coordinate quantities - do reverse
+     * (optical) left/right: coordinate quantities - do not reverse
+     * low/high: coordinate quantities - do not reverse
+     * min/max: coordinate quantities - do not reverse
+     * scroll offset - coordinate quantities - do not reverse
+     * first/last: positional indices - do not reverse
+     * front/end: positional indices - do not reverse
+     * prepend/append: related to positional indices - do not reverse
+     *
+     * Note that although quantities do not reverse in RTL flows, their
+     * relationship does.  In LTR flows, the first positional index is
+     * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
+     * positional quantities are mapped onto coordinate quantities,
+     * the flow must be checked and the logic reversed.
+     */
+
+    /**
      * The orientation of a "row".
      */
     private int mOrientation = HORIZONTAL;
@@ -186,6 +357,8 @@
 
     private OnChildSelectedListener mChildSelectedListener = null;
 
+    private OnChildLaidOutListener mChildLaidOutListener = null;
+
     /**
      * The focused position, it's not the currently visually aligned position
      * but it is the final position that we intend to focus on. If there are
@@ -194,6 +367,11 @@
     private int mFocusPosition = NO_POSITION;
 
     /**
+     * LinearSmoothScroller that consume pending DPAD movements.
+     */
+    private PendingMoveSmoothScroller mPendingMoveSmoothScroller;
+
+    /**
      * The offset to be applied to mFocusPosition, due to adapter change, on the next
      * layout.  Set to Integer.MIN_VALUE means item was removed.
      * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
@@ -202,7 +380,7 @@
     private int mFocusPositionOffset = 0;
 
     /**
-     * Force a full layout under certain situations.
+     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
      */
     private boolean mForceFullLayout;
 
@@ -269,7 +447,7 @@
     /**
      * How to position child in secondary direction.
      */
-    private int mGravity = Gravity.LEFT | Gravity.TOP;
+    private int mGravity = Gravity.START | Gravity.TOP;
     /**
      * The number of rows in the grid.
      */
@@ -281,22 +459,9 @@
     private int mNumRowsRequested = 1;
 
     /**
-     * Tracking start/end position of each row for visible items.
-     */
-    private StaggeredGrid.Row[] mRows;
-
-    /**
      * Saves grid information of each view.
      */
-    private StaggeredGrid mGrid;
-    /**
-     * Position of first item (included) that has attached views.
-     */
-    private int mFirstVisiblePos;
-    /**
-     * Position of last item (included) that has attached views.
-     */
-    private int mLastVisiblePos;
+    Grid mGrid;
 
     /**
      * Focus Scroll strategy.
@@ -344,11 +509,19 @@
     private boolean mScrollEnabled = true;
 
     /**
-     * Percent of overreach.
+     * Temporary variable: an int array of length=2.
      */
-    private float mPrimaryOverReach = 1f;
+    private static int[] sTwoInts = new int[2];
 
-    private int[] mTempDeltas = new int[2];
+    /**
+     * Set to true for RTL layout in horizontal orientation
+     */
+    private boolean mReverseFlowPrimary = false;
+
+    /**
+     * Set to true for RTL layout in vertical orientation
+     */
+    private boolean mReverseFlowSecondary = false;
 
     /**
      * Temporaries used for measuring.
@@ -373,6 +546,17 @@
         mForceFullLayout = true;
     }
 
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        if (mOrientation == HORIZONTAL) {
+            mReverseFlowPrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+            mReverseFlowSecondary = false;
+        } else {
+            mReverseFlowSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+            mReverseFlowPrimary = false;
+        }
+        mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
+    }
+
     public int getFocusScrollStrategy() {
         return mFocusScrollStrategy;
     }
@@ -504,12 +688,8 @@
         mChildSelectedListener = listener;
     }
 
-    public void setPrimaryOverReach(float fraction) {
-        if (fraction != mPrimaryOverReach) {
-            if (DEBUG) Log.v(getTag(), "setPrimaryOverReach " + fraction);
-            mPrimaryOverReach = fraction;
-            requestLayout();
-        }
+    void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
+        mChildLaidOutListener = listener;
     }
 
     private int getPositionByView(View view) {
@@ -532,16 +712,36 @@
         if (mChildSelectedListener == null) {
             return;
         }
-        if (mFocusPosition != NO_POSITION) {
-            View view = findViewByPosition(mFocusPosition);
-            if (view != null) {
-                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
-                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
-                        vh == null? NO_ID: vh.getItemId());
-                return;
+
+        if (TRACE) TraceHelper.beginSection("onChildSelected");
+        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
+        if (view != null) {
+            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+            mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
+                    vh == null? NO_ID: vh.getItemId());
+        } else {
+            mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+        }
+        if (TRACE) TraceHelper.endSection();
+
+        // Children may request layout when a child selection event occurs (such as a change of
+        // padding on the current and previously selected rows).
+        // If in layout, a child requesting layout may have been laid out before the selection
+        // callback.
+        // If it was not, the child will be laid out after the selection callback.
+        // If so, the layout request will be honoured though the view system will emit a double-
+        // layout warning.
+        // If not in layout, we may be scrolling in which case the child layout request will be
+        // eaten by recyclerview.  Post a requestLayout.
+        if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                if (getChildAt(i).isLayoutRequested()) {
+                    forceRequestLayout();
+                    break;
+                }
             }
         }
-        mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
     }
 
     @Override
@@ -651,128 +851,56 @@
      * Re-initialize data structures for a data change or handling invisible
      * selection. The method tries its best to preserve position information so
      * that staggered grid looks same before and after re-initialize.
-     * @param focusPosition The initial focusPosition that we would like to
-     *        focus on.
-     * @return Actual position that can be focused on.
+     * @return true if can fastRelayout()
      */
-    private int init(int focusPosition) {
-
-        final int newItemCount = mState.getItemCount();
-
-        if (focusPosition == NO_POSITION && newItemCount > 0) {
-            // if focus position is never set before,  initialize it to 0
-            focusPosition = 0;
-        }
-        // If adapter has changed then caches are invalid; otherwise,
-        // we try to maintain each row's position if number of rows keeps the same
-        // and existing mGrid contains the focusPosition.
-        if (mRows != null && mNumRows == mRows.length &&
-                mGrid != null && mGrid.getSize() > 0 && focusPosition >= 0 &&
-                focusPosition >= mGrid.getFirstIndex() &&
-                focusPosition <= mGrid.getLastIndex()) {
-            // strip mGrid to a subset (like a column) that contains focusPosition
-            mGrid.stripDownTo(focusPosition);
-            // make sure that remaining items do not exceed new adapter size
-            int firstIndex = mGrid.getFirstIndex();
-            int lastIndex = mGrid.getLastIndex();
-            if (DEBUG) {
-                Log .v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex " + lastIndex);
-            }
-            for (int i = lastIndex; i >=firstIndex; i--) {
-                if (i >= newItemCount) {
-                    mGrid.removeLast();
-                }
-            }
-            if (mGrid.getSize() == 0) {
-                focusPosition = newItemCount - 1;
-                // initialize row start locations
-                for (int i = 0; i < mNumRows; i++) {
-                    mRows[i].low = 0;
-                    mRows[i].high = 0;
-                }
-                if (DEBUG) Log.v(getTag(), "mGrid zero size");
-            } else {
-                // initialize row start locations
-                for (int i = 0; i < mNumRows; i++) {
-                    mRows[i].low = Integer.MAX_VALUE;
-                    mRows[i].high = Integer.MIN_VALUE;
-                }
-                firstIndex = mGrid.getFirstIndex();
-                lastIndex = mGrid.getLastIndex();
-                if (focusPosition > lastIndex) {
-                    focusPosition = mGrid.getLastIndex();
-                }
-                if (DEBUG) {
-                    Log.v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex "
-                        + lastIndex + " focusPosition " + focusPosition);
-                }
-                // fill rows with minimal view positions of the subset
-                for (int i = firstIndex; i <= lastIndex; i++) {
-                    View v = findViewByPosition(i);
-                    if (v == null) {
-                        continue;
-                    }
-                    int row = mGrid.getLocation(i).row;
-                    int low = getViewMin(v) + mScrollOffsetPrimary;
-                    if (low < mRows[row].low) {
-                        mRows[row].low = mRows[row].high = low;
-                    }
-                }
-                int firstItemRowPosition = mRows[mGrid.getLocation(firstIndex).row].low;
-                if (firstItemRowPosition == Integer.MAX_VALUE) {
-                    firstItemRowPosition = 0;
-                }
-                if (mState.didStructureChange()) {
-                    // if there is structure change, the removed item might be in the
-                    // subset,  so it is meaningless to maintain the low locations.
-                    for (int i = 0; i < mNumRows; i++) {
-                        mRows[i].low = firstItemRowPosition;
-                        mRows[i].high = firstItemRowPosition;
-                    }
-                } else {
-                    // fill other rows that does not include the subset using first item
-                    for (int i = 0; i < mNumRows; i++) {
-                        if (mRows[i].low == Integer.MAX_VALUE) {
-                            mRows[i].low = mRows[i].high = firstItemRowPosition;
-                        }
-                    }
-                }
-            }
-
-            // Same adapter, we can reuse any attached views
-            detachAndScrapAttachedViews(mRecycler);
-
+    private boolean layoutInit() {
+        if (!mState.didStructureChange() && !mForceFullLayout && mGrid != null) {
+            updateScrollController();
+            updateScrollSecondAxis();
+            mGrid.setMargin(mMarginPrimary);
+            return true;
         } else {
-            // otherwise recreate data structure
-            mRows = new StaggeredGrid.Row[mNumRows];
-
-            for (int i = 0; i < mNumRows; i++) {
-                mRows[i] = new StaggeredGrid.Row();
-            }
-            mGrid = new StaggeredGridDefault();
+            mForceFullLayout = false;
+            boolean focusViewWasInTree = mGrid != null && mFocusPosition >= 0
+                    && mFocusPosition >= mGrid.getFirstVisibleIndex()
+                    && mFocusPosition <= mGrid.getLastVisibleIndex();
+            int firstVisibleIndex = focusViewWasInTree ? mGrid.getFirstVisibleIndex() : 0;
+            final int newItemCount = mState.getItemCount();
             if (newItemCount == 0) {
-                focusPosition = NO_POSITION;
-            } else if (focusPosition >= newItemCount) {
-                focusPosition = newItemCount - 1;
+                mFocusPosition = NO_POSITION;
+            } else if (mFocusPosition >= newItemCount) {
+                mFocusPosition = newItemCount - 1;
+            } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
+                // if focus position is never set before,  initialize it to 0
+                mFocusPosition = 0;
             }
 
-            // Adapter may have changed so remove all attached views permanently
-            removeAndRecycleAllViews(mRecycler);
-
-            mScrollOffsetPrimary = 0;
-            mScrollOffsetSecondary = 0;
-            mWindowAlignment.reset();
+            if (mGrid == null || mNumRows != mGrid.getNumRows() ||
+                    mReverseFlowPrimary != mGrid.isReversedFlow()) {
+                mGrid = Grid.createStaggeredMultipleRows(mNumRows);
+                mGrid.setProvider(mGridProvider);
+                mGrid.setReversedFlow(mReverseFlowPrimary);
+            }
+            initScrollController();
+            updateScrollSecondAxis();
+            mGrid.setMargin(mMarginPrimary);
+            detachAndScrapAttachedViews(mRecycler);
+            mGrid.resetVisibleIndex();
+            if (mFocusPosition == NO_POSITION) {
+                mBaseGridView.clearFocus();
+            }
+            mWindowAlignment.mainAxis().invalidateScrollMin();
+            mWindowAlignment.mainAxis().invalidateScrollMax();
+            if (focusViewWasInTree && firstVisibleIndex <= mFocusPosition) {
+                // if focusView was in tree, we will add item from first visible item
+                mGrid.setStart(firstVisibleIndex);
+            } else {
+                // if focusView was not in tree, it's probably because focus position jumped
+                // far away from visible range,  so use mFocusPosition as start
+                mGrid.setStart(mFocusPosition);
+            }
+            return false;
         }
-
-        mGrid.setProvider(mGridProvider);
-        // mGrid share the same Row array information
-        mGrid.setRows(mRows);
-        mFirstVisiblePos = mLastVisiblePos = NO_POSITION;
-
-        initScrollController();
-        updateScrollSecondAxis();
-
-        return focusPosition;
     }
 
     private int getRowSizeSecondary(int rowIndex) {
@@ -787,14 +915,23 @@
 
     private int getRowStartSecondary(int rowIndex) {
         int start = 0;
-        for (int i = 0; i < rowIndex; i++) {
-            start += getRowSizeSecondary(i) + mMarginSecondary;
+        // Iterate from left to right, which is a different index traversal
+        // in RTL flow
+        if (mReverseFlowSecondary) {
+            for (int i = mNumRows-1; i > rowIndex; i--) {
+                start += getRowSizeSecondary(i) + mMarginSecondary;
+            }
+        } else {
+            for (int i = 0; i < rowIndex; i++) {
+                start += getRowSizeSecondary(i) + mMarginSecondary;
+            }
         }
         return start;
     }
 
     private int getSizeSecondary() {
-        return getRowStartSecondary(mNumRows - 1) + getRowSizeSecondary(mNumRows - 1);
+        int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
+        return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
     }
 
     private void measureScrapChild(int position, int widthSpec, int heightSpec,
@@ -818,36 +955,48 @@
             return false;
         }
 
-        List<Integer>[] rows = mGrid == null ? null :
-            mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
+        if (TRACE) TraceHelper.beginSection("processRowSizeSecondary");
+        CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
         boolean changed = false;
         int scrapChildWidth = -1;
         int scrapChildHeight = -1;
 
         for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
-            final int rowItemCount = rows == null ? 0 : rows[rowIndex].size();
-            if (DEBUG) Log.v(getTag(), "processRowSizeSecondary row " + rowIndex +
-                    " rowItemCount " + rowItemCount);
-
+            CircularIntArray row = rows == null ? null : rows[rowIndex];
+            final int rowItemsPairCount = row == null ? 0 : row.size();
             int rowSize = -1;
-            for (int i = 0; i < rowItemCount; i++) {
-                final View view = findViewByPosition(rows[rowIndex].get(i));
-                if (view == null) {
-                    continue;
-                }
-                if (measure && view.isLayoutRequested()) {
-                    measureChild(view);
-                }
-                final int secondarySize = mOrientation == HORIZONTAL ?
-                        view.getMeasuredHeight() : view.getMeasuredWidth();
-                if (secondarySize > rowSize) {
-                    rowSize = secondarySize;
+            for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
+                    rowItemPairIndex += 2) {
+                final int rowIndexStart = row.get(rowItemPairIndex);
+                final int rowIndexEnd = row.get(rowItemPairIndex + 1);
+                for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
+                    final View view = findViewByPosition(i);
+                    if (view == null) {
+                        continue;
+                    }
+                    if (measure && view.isLayoutRequested()) {
+                        measureChild(view);
+                    }
+                    final int secondarySize = mOrientation == HORIZONTAL ?
+                            view.getMeasuredHeight() : view.getMeasuredWidth();
+                    if (secondarySize > rowSize) {
+                        rowSize = secondarySize;
+                    }
                 }
             }
 
-            if (measure && rowSize < 0 && mState.getItemCount() > 0) {
+            final int itemCount = mState.getItemCount();
+            if (measure && rowSize < 0 && itemCount > 0) {
                 if (scrapChildWidth < 0 && scrapChildHeight < 0) {
-                    measureScrapChild(mFocusPosition == NO_POSITION ? 0 : mFocusPosition,
+                    int position;
+                    if (mFocusPosition == NO_POSITION) {
+                        position = 0;
+                    } else if (mFocusPosition >= itemCount) {
+                        position = itemCount - 1;
+                    } else {
+                        position = mFocusPosition;
+                    }
+                    measureScrapChild(position,
                             MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                             MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                             mMeasuredDimension);
@@ -858,23 +1007,18 @@
                 }
                 rowSize = mOrientation == HORIZONTAL ? scrapChildHeight : scrapChildWidth;
             }
-
             if (rowSize < 0) {
                 rowSize = 0;
             }
-
-            if (DEBUG) Log.v(getTag(), "row " + rowIndex + " rowItemCount " + rowItemCount +
-                    " rowSize " + rowSize);
-
             if (mRowSizeSecondary[rowIndex] != rowSize) {
                 if (DEBUG) Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex] +
                         ", " + rowSize);
-
                 mRowSizeSecondary[rowIndex] = rowSize;
                 changed = true;
             }
         }
 
+        if (TRACE) TraceHelper.endSection();
         return changed;
     }
 
@@ -1016,11 +1160,11 @@
                     " mFixedRowSizeSecondary " + mFixedRowSizeSecondary +
                     " mNumRows " + mNumRows);
         }
-
         leaveContext();
     }
 
     private void measureChild(View child) {
+        if (TRACE) TraceHelper.beginSection("measureChild");
         final ViewGroup.LayoutParams lp = child.getLayoutParams();
         final int secondarySpec = (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) ?
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) :
@@ -1038,18 +1182,17 @@
                     0, lp.height);
             widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, 0, lp.width);
         }
-
         child.measure(widthSpec, heightSpec);
-
         if (DEBUG) Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec) +
                 " widthSpec " + Integer.toHexString(widthSpec) +
                 " heightSpec " + Integer.toHexString(heightSpec) +
                 " measuredWidth " + child.getMeasuredWidth() +
                 " measuredHeight " + child.getMeasuredHeight());
         if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
+        if (TRACE) TraceHelper.endSection();
     }
 
-    private StaggeredGrid.Provider mGridProvider = new StaggeredGrid.Provider() {
+    private Grid.Provider mGridProvider = new Grid.Provider() {
 
         @Override
         public int getCount() {
@@ -1057,25 +1200,20 @@
         }
 
         @Override
-        public void createItem(int index, int rowIndex, boolean append) {
+        public int createItem(int index, boolean append, Object[] item) {
+            if (TRACE) TraceHelper.beginSection("createItem");
+            if (TRACE) TraceHelper.beginSection("getview");
             View v = getViewForPosition(index);
-            if (mFirstVisiblePos >= 0) {
-                // when StaggeredGrid append or prepend item, we must guarantee
-                // that sibling item has created views already.
-                if (append && index != mLastVisiblePos + 1) {
-                    throw new RuntimeException();
-                } else if (!append && index != mFirstVisiblePos - 1) {
-                    throw new RuntimeException();
-                }
-            }
-
+            if (TRACE) TraceHelper.endSection();
             // See recyclerView docs:  we don't need re-add scraped view if it was removed.
             if (!((RecyclerView.LayoutParams) v.getLayoutParams()).isItemRemoved()) {
+                if (TRACE) TraceHelper.beginSection("addView");
                 if (append) {
                     addView(v);
                 } else {
                     addView(v, 0);
                 }
+                if (TRACE) TraceHelper.endSection();
                 if (mChildVisibility != -1) {
                     v.setVisibility(mChildVisibility);
                 }
@@ -1084,93 +1222,100 @@
                 if (mInLayout && index == mFocusPosition) {
                     dispatchChildSelected();
                 }
-
                 measureChild(v);
             }
+            item[0] = v;
+            return mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
+        }
 
-            int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
+        @Override
+        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
+            View v = (View) item;
             int start, end;
-            final boolean rowIsEmpty = mRows[rowIndex].high == mRows[rowIndex].low;
-            if (append) {
-                if (!rowIsEmpty) {
-                    // if there are existing item in the row,  add margin between
-                    start = mRows[rowIndex].high + mMarginPrimary;
-                } else {
-                    if (mLastVisiblePos >= 0) {
-                        int lastRow = mGrid.getLocation(mLastVisiblePos).row;
-                        // if the last visible item is not last row,  align to beginning,
-                        // otherwise start a new column after.
-                        if (lastRow < mNumRows - 1) {
-                            start = mRows[lastRow].low;
-                        } else {
-                            start = mRows[lastRow].high + mMarginPrimary;
-                        }
-                    } else {
-                        start = 0;
-                    }
-                    mRows[rowIndex].low = start;
-                }
-                end = start + length;
-                mRows[rowIndex].high = end;
-            } else {
-                if (!rowIsEmpty) {
-                    // if there are existing item in the row,  add margin between
-                    end = mRows[rowIndex].low - mMarginPrimary;
-                    start = end - length;
-                } else {
-                    if (mFirstVisiblePos >= 0) {
-                        int firstRow = mGrid.getLocation(mFirstVisiblePos).row;
-                        // if the first visible item is not first row,  align to beginning,
-                        // otherwise start a new column before.
-                        if (firstRow > 0) {
-                            start = mRows[firstRow].low;
-                            end = start + length;
-                        } else {
-                            end = mRows[firstRow].low - mMarginPrimary;
-                            start = end - length;
-                        }
-                    } else {
-                        start = 0;
-                        end = length;
-                    }
-                    mRows[rowIndex].high = end;
-                }
-                mRows[rowIndex].low = start;
+            if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
+                edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingLow()
+                        : mWindowAlignment.mainAxis().getSize()
+                                - mWindowAlignment.mainAxis().getPaddingHigh();
             }
-            if (mFirstVisiblePos < 0) {
-                mFirstVisiblePos = mLastVisiblePos = index;
+            boolean edgeIsMin = !mGrid.isReversedFlow();
+            if (edgeIsMin) {
+                start = edge;
+                end = edge + length;
             } else {
-                if (append) {
-                    mLastVisiblePos++;
-                } else {
-                    mFirstVisiblePos--;
-                }
+                start = edge - length;
+                end = edge;
             }
-            if (DEBUG) Log.v(getTag(), "start " + start + " end " + end);
             int startSecondary = getRowStartSecondary(rowIndex) - mScrollOffsetSecondary;
             mChildrenStates.loadView(v, index);
-            layoutChild(rowIndex, v, start - mScrollOffsetPrimary, end - mScrollOffsetPrimary,
-                    startSecondary);
+            layoutChild(rowIndex, v, start, end, startSecondary);
             if (DEBUG) {
                 Log.d(getTag(), "addView " + index + " " + v);
             }
-            if (index == mFirstVisiblePos) {
-                updateScrollMin();
+            if (TRACE) TraceHelper.endSection();
+
+            if (index == mGrid.getFirstVisibleIndex()) {
+                if (!mGrid.isReversedFlow()) {
+                    updateScrollMin();
+                } else {
+                    updateScrollMax();
+                }
             }
-            if (index == mLastVisiblePos) {
-                updateScrollMax();
+            if (index == mGrid.getLastVisibleIndex()) {
+                if (!mGrid.isReversedFlow()) {
+                    updateScrollMax();
+                } else {
+                    updateScrollMin();
+                }
             }
+            if (!mInLayout && mPendingMoveSmoothScroller != null) {
+                mPendingMoveSmoothScroller.consumePendingMoves();
+            }
+            if (mChildLaidOutListener != null) {
+                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
+                mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
+                        vh == null ? NO_ID : vh.getItemId());
+            }
+        }
+
+        @Override
+        public void removeItem(int index) {
+            if (TRACE) TraceHelper.beginSection("removeItem");
+            View v = findViewByPosition(index);
+            if (mInLayout) {
+                detachAndScrapView(v, mRecycler);
+            } else {
+                removeAndRecycleView(v, mRecycler);
+            }
+            if (TRACE) TraceHelper.endSection();
+        }
+
+        @Override
+        public int getEdge(int index) {
+            if (mReverseFlowPrimary) {
+                return getViewMax(findViewByPosition(index));
+            } else {
+                return getViewMin(findViewByPosition(index));
+            }
+        }
+
+        @Override
+        public int getSize(int index) {
+            final View v = findViewByPosition(index);
+            return mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
         }
     };
 
     private void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
+        if (TRACE) TraceHelper.beginSection("layoutChild");
         int sizeSecondary = mOrientation == HORIZONTAL ? v.getMeasuredHeight()
                 : v.getMeasuredWidth();
         if (mFixedRowSizeSecondary > 0) {
             sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
         }
         final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
-        final int horizontalGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary) ?
+                Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK, View.LAYOUT_DIRECTION_RTL) :
+                mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
                 || mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
             // do nothing
@@ -1196,6 +1341,7 @@
         v.layout(left, top, right, bottom);
         updateChildOpticalInsets(v, left, top, right, bottom);
         updateChildAlignments(v);
+        if (TRACE) TraceHelper.endSection();
     }
 
     private void updateChildOpticalInsets(View v, int left, int top, int right, int bottom) {
@@ -1216,246 +1362,112 @@
         }
     }
 
-    private boolean needsAppendVisibleItem() {
-        if (mLastVisiblePos < mFocusPosition) {
-            return true;
-        }
-        int right = mScrollOffsetPrimary + mSizePrimary;
-        for (int i = 0; i < mNumRows; i++) {
-            if (mRows[i].low == mRows[i].high) {
-                if (mRows[i].high < right) {
-                    return true;
-                }
-            } else if (mRows[i].high < right - mMarginPrimary) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean needsPrependVisibleItem() {
-        if (mFirstVisiblePos > mFocusPosition) {
-            return true;
-        }
-        for (int i = 0; i < mNumRows; i++) {
-            if (mRows[i].low == mRows[i].high) {
-                if (mRows[i].low > mScrollOffsetPrimary) {
-                    return true;
-                }
-            } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Append one column if possible and return true if reach end.
-    private boolean appendOneVisibleItem() {
-        while (true) {
-            if (mLastVisiblePos != NO_POSITION && mLastVisiblePos < mState.getItemCount() -1 &&
-                    mLastVisiblePos < mGrid.getLastIndex()) {
-                // append invisible view of saved location till last row
-                final int index = mLastVisiblePos + 1;
-                final int row = mGrid.getLocation(index).row;
-                mGridProvider.createItem(index, row, true);
-                if (row == mNumRows - 1) {
-                    return false;
-                }
-            } else if ((mLastVisiblePos == NO_POSITION && mState.getItemCount() > 0) ||
-                    (mLastVisiblePos != NO_POSITION &&
-                            mLastVisiblePos < mState.getItemCount() - 1)) {
-                mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
-                return false;
-            } else {
-                return true;
-            }
-        }
-    }
-
-    private void appendVisibleItems() {
-        while (needsAppendVisibleItem()) {
-            if (appendOneVisibleItem()) {
-                break;
-            }
-        }
-    }
-
-    // Prepend one column if possible and return true if reach end.
-    private boolean prependOneVisibleItem() {
-        while (true) {
-            if (mFirstVisiblePos > 0) {
-                if (mFirstVisiblePos > mGrid.getFirstIndex()) {
-                    // prepend invisible view of saved location till first row
-                    final int index = mFirstVisiblePos - 1;
-                    final int row = mGrid.getLocation(index).row;
-                    mGridProvider.createItem(index, row, false);
-                    if (row == 0) {
-                        return false;
-                    }
-                } else {
-                    mGrid.prependItems(mScrollOffsetPrimary);
-                    return false;
-                }
-            } else {
-                return true;
-            }
-        }
-    }
-
-    private void prependVisibleItems() {
-        while (needsPrependVisibleItem()) {
-            if (prependOneVisibleItem()) {
-                break;
-            }
-        }
-    }
-
-    private void removeChildAt(int position) {
-        View v = findViewByPosition(position);
-        if (v != null) {
-            if (DEBUG) {
-                Log.d(getTag(), "removeAndRecycleViewAt " + position + " " + v);
-            }
-            mChildrenStates.saveOffscreenView(v, position);
-            removeAndRecycleView(v, mRecycler);
-        }
-    }
-
     private void removeInvisibleViewsAtEnd() {
-        if (!mPruneChild) {
-            return;
-        }
-        boolean update = false;
-        while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
-            View view = findViewByPosition(mLastVisiblePos);
-            if (getViewMin(view) > mSizePrimary) {
-                removeChildAt(mLastVisiblePos);
-                mLastVisiblePos--;
-                update = true;
-            } else {
-                break;
-            }
-        }
-        if (update) {
-            updateRowsMinMax();
+        if (mPruneChild) {
+            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
+                    mReverseFlowPrimary ? 0 : mSizePrimary);
         }
     }
 
     private void removeInvisibleViewsAtFront() {
-        if (!mPruneChild) {
-            return;
+        if (mPruneChild) {
+            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
+                    mReverseFlowPrimary ? mSizePrimary : 0);
         }
-        boolean update = false;
-        while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
-            View view = findViewByPosition(mFirstVisiblePos);
-            if (getViewMax(view) < 0) {
-                removeChildAt(mFirstVisiblePos);
-                mFirstVisiblePos++;
-                update = true;
+    }
+
+    private boolean appendOneColumnVisibleItems() {
+        return mGrid.appendOneColumnVisibleItems();
+    }
+
+    private boolean prependOneColumnVisibleItems() {
+        return mGrid.prependOneColumnVisibleItems();
+    }
+
+    private void appendVisibleItems() {
+        mGrid.appendVisibleItems(mReverseFlowPrimary ? 0 : mSizePrimary);
+    }
+
+    private void prependVisibleItems() {
+        mGrid.prependVisibleItems(mReverseFlowPrimary ? mSizePrimary : 0);
+    }
+
+    /**
+     * Fast layout when there is no structure change, adapter change, etc.
+     * It will layout all views was layout requested or updated, until hit a view
+     * with different size,  then it break and detachAndScrap all views after that. 
+     */
+    private void fastRelayout() {
+        boolean invalidateAfter = false;
+        final int childCount = getChildCount();
+        int position = -1;
+        for (int index = 0; index < childCount; index++) {
+            View view = getChildAt(index);
+            position = getPositionByIndex(index);
+            Grid.Location location = mGrid.getLocation(position);
+            if (location == null) {
+                if (DEBUG) Log.w(getTag(), "fastRelayout(): no Location at " + position);
+                invalidateAfter = true;
+                break;
+            }
+
+            int startSecondary = getRowStartSecondary(location.row) - mScrollOffsetSecondary;
+            int primarySize, end;
+            int start = getViewMin(view);
+            int oldPrimarySize = (mOrientation == HORIZONTAL) ?
+                    view.getMeasuredWidth() :
+                    view.getMeasuredHeight();
+
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (lp.viewNeedsUpdate()) {
+                int viewIndex = mBaseGridView.indexOfChild(view);
+                detachAndScrapView(view, mRecycler);
+                view = getViewForPosition(position);
+                addView(view, viewIndex);
+            }
+
+            if (view.isLayoutRequested()) {
+                measureChild(view);
+            }
+            if (mOrientation == HORIZONTAL) {
+                primarySize = view.getMeasuredWidth();
+                end = start + primarySize;
             } else {
+                primarySize = view.getMeasuredHeight();
+                end = start + primarySize;
+            }
+            layoutChild(location.row, view, start, end, startSecondary);
+            if (oldPrimarySize != primarySize) {
+                // size changed invalidate remaining Locations
+                if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
+                invalidateAfter = true;
                 break;
             }
         }
-        if (update) {
-            updateRowsMinMax();
-        }
-    }
-
-    private void updateRowsMinMax() {
-        if (mFirstVisiblePos < 0) {
-            return;
-        }
-        for (int i = 0; i < mNumRows; i++) {
-            mRows[i].low = Integer.MAX_VALUE;
-            mRows[i].high = Integer.MIN_VALUE;
-        }
-        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
-            View view = findViewByPosition(i);
-            int row = mGrid.getLocation(i).row;
-            int low = getViewMin(view) + mScrollOffsetPrimary;
-            if (low < mRows[row].low) {
-                mRows[row].low = low;
-            }
-            int high = getViewMax(view) + mScrollOffsetPrimary;
-            if (high > mRows[row].high) {
-                mRows[row].high = high;
+        if (invalidateAfter) {
+            final int savedLastPos = mGrid.getLastVisibleIndex();
+            mGrid.invalidateItemsAfter(position);
+            if (mPruneChild) {
+                // in regular prune child mode, we just append items up to edge limit
+                appendVisibleItems();
+            } else {
+                // prune disabled(e.g. in RowsFragment transition): append all removed items
+                while (mGrid.appendOneColumnVisibleItems()
+                        && mGrid.getLastVisibleIndex() < savedLastPos);
             }
         }
-    }
-
-    // Fast layout when there is no structure change, adapter change, etc.
-    protected void fastRelayout(boolean scrollToFocus) {
-        initScrollController();
-
-        List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
-
-        // relayout and repositioning views on each row
-        for (int i = 0; i < mNumRows; i++) {
-            List<Integer> row = rows[i];
-            final int startSecondary = getRowStartSecondary(i) - mScrollOffsetSecondary;
-            for (int j = 0, size = row.size(); j < size; j++) {
-                final int position = row.get(j);
-                View view = findViewByPosition(position);
-                int primaryDelta, end;
-
-                int start = getViewMin(view);
-                int oldPrimarySize = (mOrientation == HORIZONTAL) ?
-                        view.getMeasuredWidth() :
-                        view.getMeasuredHeight();
-
-                LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                if (lp.viewNeedsUpdate()) {
-                    int index = mBaseGridView.indexOfChild(view);
-                    detachAndScrapView(view, mRecycler);
-                    view = getViewForPosition(position);
-                    addView(view, index);
-                }
-
-                if (view.isLayoutRequested()) {
-                    measureChild(view);
-                }
-
-                if (mOrientation == HORIZONTAL) {
-                    end = start + view.getMeasuredWidth();
-                    primaryDelta = view.getMeasuredWidth() - oldPrimarySize;
-                    if (primaryDelta != 0) {
-                        for (int k = j + 1; k < size; k++) {
-                            findViewByPosition(row.get(k)).offsetLeftAndRight(primaryDelta);
-                        }
-                    }
-                } else {
-                    end = start + view.getMeasuredHeight();
-                    primaryDelta = view.getMeasuredHeight() - oldPrimarySize;
-                    if (primaryDelta != 0) {
-                        for (int k = j + 1; k < size; k++) {
-                            findViewByPosition(row.get(k)).offsetTopAndBottom(primaryDelta);
-                        }
-                    }
-                }
-                layoutChild(i, view, start, end, startSecondary);
-            }
-        }
-
-        updateRowsMinMax();
-        appendVisibleItems();
-        prependVisibleItems();
-
-        updateRowsMinMax();
         updateScrollMin();
         updateScrollMax();
         updateScrollSecondAxis();
-
-        if (scrollToFocus) {
-            View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 : mFocusPosition);
-            scrollToView(focusView, false);
-        }
     }
 
     public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
+        if (TRACE) TraceHelper.beginSection("removeAndRecycleAllViews");
         if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
         for (int i = getChildCount() - 1; i >= 0; i--) {
             removeAndRecycleViewAt(i, recycler);
         }
+        if (TRACE) TraceHelper.endSection();
     }
 
     // Lays out items based on the current scroll position
@@ -1493,12 +1505,14 @@
             mFocusPositionOffset = 0;
         }
         saveContext(recycler, state);
+
         // Track the old focus view so we can adjust our system scroll position
         // so that any scroll animations happening now will remain valid.
         // We must use same delta in Pre Layout (if prelayout exists) and second layout.
         // So we cache the deltas in PreLayout and use it in second layout.
         int delta = 0, deltaSecondary = 0;
-        if (mFocusPosition != NO_POSITION && scrollToFocus) {
+        if (mFocusPosition != NO_POSITION && scrollToFocus
+                && mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
             // FIXME: we should get the remaining scroll animation offset from RecyclerView
             View focusView = findViewByPosition(mFocusPosition);
             if (focusView != null) {
@@ -1510,49 +1524,24 @@
             }
         }
 
-        final boolean hasDoneFirstLayout = hasDoneFirstLayout();
+        boolean hadFocus = mBaseGridView.hasFocus();
         int savedFocusPos = mFocusPosition;
-        boolean fastRelayout = false;
-        if (!mState.didStructureChange() && !mForceFullLayout && hasDoneFirstLayout) {
-            fastRelayout = true;
-            fastRelayout(scrollToFocus);
+        boolean fastRelayout;
+        if (fastRelayout = layoutInit()) {
+            fastRelayout();
+            View focusView = findViewByPosition(mFocusPosition);
+            if (scrollToFocus) {
+                scrollToView(focusView, false);
+            }
+            if (focusView != null && hadFocus) {
+                focusView.requestFocus();
+            }
         } else {
-            boolean hadFocus = mBaseGridView.hasFocus();
-
-            mFocusPosition = init(mFocusPosition);
-            if (mFocusPosition != savedFocusPos) {
-                if (DEBUG) Log.v(getTag(), "savedFocusPos " + savedFocusPos +
-                        " mFocusPosition " + mFocusPosition);
+            if (mFocusPosition != NO_POSITION) {
+                // appends items till focus position.
+                while (appendOneColumnVisibleItems()
+                        && findViewByPosition(mFocusPosition) == null) ;
             }
-
-            mWindowAlignment.mainAxis().invalidateScrollMin();
-            mWindowAlignment.mainAxis().invalidateScrollMax();
-            // depending on result of init(), either recreating everything
-            // or try to reuse the row start positions near mFocusPosition
-            if (mGrid.getSize() == 0) {
-                // this is a fresh creating all items, starting from
-                // mFocusPosition with a estimated row index.
-                mGrid.setStart(mFocusPosition, StaggeredGrid.START_DEFAULT);
-
-                // Can't track the old focus view
-                delta = deltaSecondary = 0;
-
-            } else {
-                // mGrid remembers Locations for the column that
-                // contains mFocusePosition and also mRows remembers start
-                // positions of each row.
-                // Manually re-create child views for that column
-                int firstIndex = mGrid.getFirstIndex();
-                int lastIndex = mGrid.getLastIndex();
-                for (int i = firstIndex; i <= lastIndex; i++) {
-                    mGridProvider.createItem(i, mGrid.getLocation(i).row, true);
-                }
-            }
-
-            // add visible views at end until reach the end of window
-            appendVisibleItems();
-            // add visible views at front until reach the start of window
-            prependVisibleItems();
             // multiple rounds: scrollToView of first round may drag first/last child into
             // "visible window" and we update scrollMin/scrollMax then run second scrollToView
             int oldFirstVisible;
@@ -1560,8 +1549,8 @@
             do {
                 updateScrollMin();
                 updateScrollMax();
-                oldFirstVisible = mFirstVisiblePos;
-                oldLastVisible = mLastVisiblePos;
+                oldFirstVisible = mGrid.getFirstVisibleIndex();
+                oldLastVisible = mGrid.getLastVisibleIndex();
                 View focusView = findViewByPosition(mFocusPosition);
                 // we need force to initialize the child view's position
                 scrollToView(focusView, false);
@@ -1572,9 +1561,9 @@
                 prependVisibleItems();
                 removeInvisibleViewsAtFront();
                 removeInvisibleViewsAtEnd();
-            } while (mFirstVisiblePos != oldFirstVisible || mLastVisiblePos != oldLastVisible);
+            } while (mGrid.getFirstVisibleIndex() != oldFirstVisible ||
+                    mGrid.getLastVisibleIndex() != oldLastVisible);
         }
-        mForceFullLayout = false;
 
         if (scrollToFocus) {
             scrollDirectionPrimary(-delta);
@@ -1601,6 +1590,7 @@
         if (fastRelayout && mFocusPosition != savedFocusPos) {
             dispatchChildSelected();
         }
+
         mInLayout = false;
         leaveContext();
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
@@ -1668,53 +1658,62 @@
 
     // scroll in main direction may add/prune views
     private int scrollDirectionPrimary(int da) {
+        if (TRACE) TraceHelper.beginSection("scrollPrimary");
+        boolean isMaxUnknown = false, isMinUnknown = false;
+        int minScroll = 0, maxScroll = 0;
         if (da > 0) {
-            if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
-                int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+            isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
+            if (!isMaxUnknown) {
+                maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
                 if (mScrollOffsetPrimary + da > maxScroll) {
                     da = maxScroll - mScrollOffsetPrimary;
                 }
             }
         } else if (da < 0) {
-            if (!mWindowAlignment.mainAxis().isMinUnknown()) {
-                int minScroll = mWindowAlignment.mainAxis().getMinScroll();
+            isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
+            if (!isMinUnknown) {
+                minScroll = mWindowAlignment.mainAxis().getMinScroll();
                 if (mScrollOffsetPrimary + da < minScroll) {
                     da = minScroll - mScrollOffsetPrimary;
                 }
             }
         }
         if (da == 0) {
+            if (TRACE) TraceHelper.endSection();
             return 0;
         }
         offsetChildrenPrimary(-da);
         mScrollOffsetPrimary += da;
         if (mInLayout) {
+            if (TRACE) TraceHelper.endSection();
             return da;
         }
 
         int childCount = getChildCount();
         boolean updated;
 
-        if (da > 0) {
-            appendVisibleItems();
-        } else if (da < 0) {
+        if (mReverseFlowPrimary ? da > 0 : da < 0) {
             prependVisibleItems();
+        } else {
+            appendVisibleItems();
         }
         updated = getChildCount() > childCount;
         childCount = getChildCount();
 
-        if (da > 0) {
-            removeInvisibleViewsAtFront();
-        } else if (da < 0) {
+        if (TRACE) TraceHelper.beginSection("remove");
+        if (mReverseFlowPrimary ? da > 0 : da < 0) {
             removeInvisibleViewsAtEnd();
+        } else {
+            removeInvisibleViewsAtFront();
         }
+        if (TRACE) TraceHelper.endSection();
         updated |= getChildCount() < childCount;
-
         if (updated) {
             updateRowSecondarySizeRefresh();
         }
 
         mBaseGridView.invalidate();
+        if (TRACE) TraceHelper.endSection();
         return da;
     }
 
@@ -1730,34 +1729,26 @@
     }
 
     private void updateScrollMax() {
-        if (mLastVisiblePos < 0) {
+        int highVisiblePos = (!mReverseFlowPrimary) ? mGrid.getLastVisibleIndex()
+                : mGrid.getFirstVisibleIndex();
+        int highMaxPos = (!mReverseFlowPrimary) ? mState.getItemCount() - 1 : 0;
+        if (highVisiblePos < 0) {
             return;
         }
-        final boolean lastAvailable = mLastVisiblePos == mState.getItemCount() - 1;
+        final boolean highAvailable = highVisiblePos == highMaxPos;
         final boolean maxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
-        if (!lastAvailable && maxUnknown) {
+        if (!highAvailable && maxUnknown) {
             return;
         }
-        int maxEdge = Integer.MIN_VALUE;
-        int rowIndex = -1;
-        for (int i = 0; i < mRows.length; i++) {
-            if (mRows[i].high > maxEdge) {
-                maxEdge = mRows[i].high;
-                rowIndex = i;
-            }
-        }
-        int maxScroll = Integer.MAX_VALUE;
-        for (int i = mLastVisiblePos; i >= mFirstVisiblePos; i--) {
-            StaggeredGrid.Location location = mGrid.getLocation(i);
-            if (location != null && location.row == rowIndex) {
-                int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
-                mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
-                maxScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
-                mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
-                break;
-            }
-        }
-        if (lastAvailable) {
+        int maxEdge = mGrid.findRowMax(true, sTwoInts) + mScrollOffsetPrimary;
+        int rowIndex = sTwoInts[0];
+        int pos = sTwoInts[1];
+        int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
+        mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
+        int maxScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
+        mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
+
+        if (highAvailable) {
             mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
             mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
             if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
@@ -1774,34 +1765,26 @@
     }
 
     private void updateScrollMin() {
-        if (mFirstVisiblePos < 0) {
+        int lowVisiblePos = (!mReverseFlowPrimary) ? mGrid.getFirstVisibleIndex()
+                : mGrid.getLastVisibleIndex();
+        int lowMinPos = (!mReverseFlowPrimary) ? 0 : mState.getItemCount() - 1;
+        if (lowVisiblePos < 0) {
             return;
         }
-        final boolean firstAvailable = mFirstVisiblePos == 0;
+        final boolean lowAvailable = lowVisiblePos == lowMinPos;
         final boolean minUnknown = mWindowAlignment.mainAxis().isMinUnknown();
-        if (!firstAvailable && minUnknown) {
+        if (!lowAvailable && minUnknown) {
             return;
         }
-        int minEdge = Integer.MAX_VALUE;
-        int rowIndex = -1;
-        for (int i = 0; i < mRows.length; i++) {
-            if (mRows[i].low < minEdge) {
-                minEdge = mRows[i].low;
-                rowIndex = i;
-            }
-        }
-        int minScroll = Integer.MIN_VALUE;
-        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
-            StaggeredGrid.Location location = mGrid.getLocation(i);
-            if (location != null && location.row == rowIndex) {
-                int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
-                mWindowAlignment.mainAxis().setMinEdge(minEdge);
-                minScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
-                mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
-                break;
-            }
-        }
-        if (firstAvailable) {
+        int minEdge = mGrid.findRowMin(false, sTwoInts) + mScrollOffsetPrimary;
+        int rowIndex = sTwoInts[0];
+        int pos = sTwoInts[1];
+        int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
+        mWindowAlignment.mainAxis().setMinEdge(minEdge);
+        int minScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
+        mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
+
+        if (lowAvailable) {
             mWindowAlignment.mainAxis().setMinEdge(minEdge);
             mWindowAlignment.mainAxis().setMinScroll(minScroll);
             if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
@@ -1823,11 +1806,28 @@
     }
 
     private void initScrollController() {
+        mWindowAlignment.reset();
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.vertical.setSize(getHeight());
+        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+        mSizePrimary = mWindowAlignment.mainAxis().getSize();
+        mScrollOffsetPrimary = -mWindowAlignment.mainAxis().getPaddingLow();
+        mScrollOffsetSecondary = -mWindowAlignment.secondAxis().getPaddingLow();
+
+        if (DEBUG) {
+            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
+                    + " mWindowAlignment " + mWindowAlignment
+                    + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
+        }
+    }
+
+    private void updateScrollController() {
         // mScrollOffsetPrimary and mScrollOffsetSecondary includes the padding.
         // e.g. when topPadding is 16 for horizontal grid view,  the initial
-        // mScrollOffsetSecondary is -16.  fastLayout() put views based on offsets(not padding),
+        // mScrollOffsetSecondary is -16.  fastRelayout() put views based on offsets(not padding),
         // when padding changes to 20,  we also need update mScrollOffsetSecondary to -20 before
-        // fastLayout() is performed
+        // fastRelayout() is performed
         int paddingPrimaryDiff, paddingSecondaryDiff;
         if (mOrientation == HORIZONTAL) {
             paddingPrimaryDiff = getPaddingLeft() - mWindowAlignment.horizontal.getPaddingLow();
@@ -1839,20 +1839,16 @@
         mScrollOffsetPrimary -= paddingPrimaryDiff;
         mScrollOffsetSecondary -= paddingSecondaryDiff;
 
-        if (mOrientation == HORIZONTAL) {
-            mWindowAlignment.horizontal.setSize((int)(getWidth() * mPrimaryOverReach + 0.5f));
-            mWindowAlignment.vertical.setSize(getHeight());
-        } else {
-            mWindowAlignment.horizontal.setSize(getWidth());
-            mWindowAlignment.vertical.setSize((int) (getHeight() * mPrimaryOverReach + 0.5f));
-        }
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.vertical.setSize(getHeight());
         mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
         mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
         mSizePrimary = mWindowAlignment.mainAxis().getSize();
 
         if (DEBUG) {
-            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
-                    + " mWindowAlignment " + mWindowAlignment);
+            Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
+                    + " mWindowAlignment " + mWindowAlignment
+                    + " mScrollOffsetPrimary " + mScrollOffsetPrimary);
         }
     }
 
@@ -1869,9 +1865,13 @@
     }
 
     public void setSelection(RecyclerView parent, int position, boolean smooth) {
-        if (mFocusPosition == position) {
-            return;
+        if (mFocusPosition != position && position != NO_POSITION) {
+            scrollToSelection(parent, position, smooth);
         }
+    }
+
+    private void scrollToSelection(RecyclerView parent, int position, boolean smooth) {
+        if (TRACE) TraceHelper.beginSection("scrollToSelection");
         View view = findViewByPosition(position);
         if (view != null) {
             mInSelection = true;
@@ -1889,58 +1889,69 @@
                             "not be called before first layout pass");
                     return;
                 }
-                LinearSmoothScroller linearSmoothScroller =
-                        new LinearSmoothScroller(parent.getContext()) {
-                    @Override
-                    public PointF computeScrollVectorForPosition(int targetPosition) {
-                        if (getChildCount() == 0) {
-                            return null;
-                        }
-                        final int firstChildPos = getPosition(getChildAt(0));
-                        final int direction = targetPosition < firstChildPos ? -1 : 1;
-                        if (mOrientation == HORIZONTAL) {
-                            return new PointF(direction, 0);
-                        } else {
-                            return new PointF(0, direction);
-                        }
-                    }
-                    @Override
-                    protected void onTargetFound(View targetView,
-                            RecyclerView.State state, Action action) {
-                        if (hasFocus()) {
-                            targetView.requestFocus();
-                        }
-                        dispatchChildSelected();
-                        if (getScrollPosition(targetView, mTempDeltas)) {
-                            int dx, dy;
-                            if (mOrientation == HORIZONTAL) {
-                                dx = mTempDeltas[0];
-                                dy = mTempDeltas[1];
-                            } else {
-                                dx = mTempDeltas[1];
-                                dy = mTempDeltas[0];
-                            }
-                            final int distance = (int) Math.sqrt(dx * dx + dy * dy);
-                            final int time = calculateTimeForDeceleration(distance);
-                            action.update(dx, dy, time, mDecelerateInterpolator);
-                        }
-                    }
-                };
-                linearSmoothScroller.setTargetPosition(position);
-                startSmoothScroll(linearSmoothScroller);
+                startPositionSmoothScroller(position);
             } else {
                 mForceFullLayout = true;
                 parent.requestLayout();
             }
         }
+        if (TRACE) TraceHelper.endSection();
+    }
+
+    void startPositionSmoothScroller(int position) {
+        LinearSmoothScroller linearSmoothScroller =
+                new GridLinearSmoothScroller() {
+            @Override
+            public PointF computeScrollVectorForPosition(int targetPosition) {
+                if (getChildCount() == 0) {
+                    return null;
+                }
+                final int firstChildPos = getPosition(getChildAt(0));
+                // TODO We should be able to deduce direction from bounds of current and target
+                // focus, rather than making assumptions about positions and directionality
+                final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
+                        : targetPosition < firstChildPos;
+                final int direction = isStart ? -1 : 1;
+                if (mOrientation == HORIZONTAL) {
+                    return new PointF(direction, 0);
+                } else {
+                    return new PointF(0, direction);
+                }
+            }
+
+        };
+        linearSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(linearSmoothScroller);
+    }
+
+    private void processPendingMovement(boolean forward) {
+        if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
+            return;
+        }
+        if (mPendingMoveSmoothScroller == null) {
+            // Stop existing scroller and create a new PendingMoveSmoothScroller.
+            mBaseGridView.stopScroll();
+            PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
+                    forward ? 1 : -1);
+            mFocusPositionOffset = 0;
+            startSmoothScroll(linearSmoothScroller);
+            if (linearSmoothScroller.isRunning()) {
+                mPendingMoveSmoothScroller = linearSmoothScroller;
+            }
+        } else {
+            if (forward) {
+                mPendingMoveSmoothScroller.increasePendingMoves();
+            } else {
+                mPendingMoveSmoothScroller.decreasePendingMoves();
+            }
+        }
     }
 
     @Override
     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
         if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
                 + positionStart + " itemCount " + itemCount);
-        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE
-                && getChildAt(mFocusPosition) != null) {
+        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
             int pos = mFocusPosition + mFocusPositionOffset;
             if (positionStart <= pos) {
                 mFocusPositionOffset += itemCount;
@@ -1958,10 +1969,9 @@
 
     @Override
     public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
+        if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
                 + positionStart + " itemCount " + itemCount);
-        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE
-                && getChildAt(mFocusPosition) != null) {
+        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
             int pos = mFocusPosition + mFocusPositionOffset;
             if (positionStart <= pos) {
                 if (positionStart + itemCount > pos) {
@@ -1978,10 +1988,9 @@
     @Override
     public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
             int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsAdded fromPosition "
+        if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
                 + fromPosition + " toPosition " + toPosition);
-        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE
-                && getChildAt(mFocusPosition) != null) {
+        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
             int pos = mFocusPosition + mFocusPositionOffset;
             if (fromPosition <= pos && pos < fromPosition + itemCount) {
                 // moved items include focused position
@@ -1999,6 +2008,8 @@
 
     @Override
     public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
+                + positionStart + " itemCount " + itemCount);
         for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
             mChildrenStates.remove(i);
         }
@@ -2009,6 +2020,11 @@
         if (mFocusSearchDisabled) {
             return true;
         }
+        if (getPositionByView(child) == NO_POSITION) {
+            // This shouldn't happen, but in case it does be sure not to attempt a
+            // scroll to a view whose item has been removed.
+            return true;
+        }
         if (!mInLayout && !mInSelection) {
             scrollToView(child, true);
         }
@@ -2041,39 +2057,49 @@
     }
 
     private int getPrimarySystemScrollPosition(View view) {
-        int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
-        int pos = getPositionByView(view);
-        StaggeredGrid.Location location = mGrid.getLocation(pos);
-        final int row = location.row;
-        boolean isFirst = mFirstVisiblePos == 0;
+        final int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
+        final int viewMin = getViewMin(view);
+        final int viewMax = getViewMax(view);
         // TODO: change to use State object in onRequestChildFocus()
-        boolean isLast = mLastVisiblePos == (mState == null ?
-                getItemCount() : mState.getItemCount()) - 1;
-        if (isFirst || isLast) {
-            for (int i = getChildCount() - 1; i >= 0; i--) {
-                int position = getPositionByIndex(i);
-                StaggeredGrid.Location loc = mGrid.getLocation(position);
-                if (loc != null && loc.row == row) {
-                    if (position < pos) {
-                        isFirst = false;
-                    } else if (position > pos) {
-                        isLast = false;
-                    }
-                }
+        boolean isMin, isMax;
+        if (!mReverseFlowPrimary) {
+            isMin = mGrid.getFirstVisibleIndex() == 0;
+            isMax = mGrid.getLastVisibleIndex() == (mState == null ?
+                    getItemCount() : mState.getItemCount()) - 1;
+        } else {
+            isMax = mGrid.getFirstVisibleIndex() == 0;
+            isMin = mGrid.getLastVisibleIndex() == (mState == null ?
+                    getItemCount() : mState.getItemCount()) - 1;
+        }
+        for (int i = getChildCount() - 1; (isMin || isMax) && i >= 0; i--) {
+            View v = getChildAt(i);
+            if (v == view || v == null) {
+                continue;
+            }
+            if (isMin && getViewMin(v) < viewMin) {
+                isMin = false;
+            }
+            if (isMax && getViewMax(v) > viewMax) {
+                isMax = false;
             }
         }
-        return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isFirst, isLast);
+        return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isMin, isMax);
     }
 
     private int getSecondarySystemScrollPosition(View view) {
         int viewCenterSecondary = mScrollOffsetSecondary + getViewCenterSecondary(view);
         int pos = getPositionByView(view);
-        StaggeredGrid.Location location = mGrid.getLocation(pos);
+        Grid.Location location = mGrid.getLocation(pos);
         final int row = location.row;
-        boolean isFirst = row == 0;
-        boolean isLast = row == mGrid.getNumRows() - 1;
-        return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary,
-                isFirst, isLast);
+        final boolean isMin, isMax;
+        if (!mReverseFlowSecondary) {
+            isMin = row == 0;
+            isMax = row == mGrid.getNumRows() - 1;
+        } else {
+            isMax = row == 0;
+            isMin = row == mGrid.getNumRows() - 1;
+        }
+        return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary, isMin, isMax);
     }
 
     /**
@@ -2099,11 +2125,11 @@
             // by setSelection())
             view.requestFocus();
         }
-        if (!mScrollEnabled) {
+        if (!mScrollEnabled && smooth) {
             return;
         }
-        if (getScrollPosition(view, mTempDeltas)) {
-            scrollGrid(mTempDeltas[0], mTempDeltas[1], smooth);
+        if (getScrollPosition(view, sTwoInts)) {
+            scrollGrid(sTwoInts[0], sTwoInts[1], smooth);
         }
     }
 
@@ -2128,20 +2154,20 @@
         View lastView = null;
         int paddingLow = mWindowAlignment.mainAxis().getPaddingLow();
         int clientSize = mWindowAlignment.mainAxis().getClientSize();
-        final int row = mGrid.getLocation(pos).row;
+        final int row = mGrid.getRowIndex(pos);
         if (viewMin < paddingLow) {
             // view enters low padding area:
             firstView = view;
             if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
                 // scroll one "page" left/top,
                 // align first visible item of the "page" at the low padding edge.
-                while (!prependOneVisibleItem()) {
-                    List<Integer> positions =
-                            mGrid.getItemPositionsInRows(mFirstVisiblePos, pos)[row];
+                while (prependOneColumnVisibleItems()) {
+                    CircularIntArray positions =
+                            mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
                     firstView = findViewByPosition(positions.get(0));
                     if (viewMax - getViewMin(firstView) > clientSize) {
-                        if (positions.size() > 1) {
-                            firstView = findViewByPosition(positions.get(1));
+                        if (positions.size() > 2) {
+                            firstView = findViewByPosition(positions.get(2));
                         }
                         break;
                     }
@@ -2153,14 +2179,14 @@
                 // scroll whole one page right/bottom, align view at the low padding edge.
                 firstView = view;
                 do {
-                    List<Integer> positions =
-                            mGrid.getItemPositionsInRows(pos, mLastVisiblePos)[row];
+                    CircularIntArray positions =
+                            mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
                     lastView = findViewByPosition(positions.get(positions.size() - 1));
                     if (getViewMax(lastView) - viewMin > clientSize) {
                         lastView = null;
                         break;
                     }
-                } while (!appendOneVisibleItem());
+                } while (appendOneColumnVisibleItems());
                 if (lastView != null) {
                     // however if we reached end,  we should align last view.
                     firstView = null;
@@ -2199,7 +2225,8 @@
         int scrollSecondary = getSecondarySystemScrollPosition(view);
         if (DEBUG) {
             Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
-                    +" " + mWindowAlignment);
+                    + " " + mWindowAlignment);
+            Log.v(getTag(), "getAlignedPosition " + mScrollOffsetPrimary + " " + mScrollOffsetSecondary);
         }
         scrollPrimary -= mScrollOffsetPrimary;
         scrollSecondary -= mScrollOffsetSecondary;
@@ -2249,12 +2276,9 @@
     public void setScrollEnabled(boolean scrollEnabled) {
         if (mScrollEnabled != scrollEnabled) {
             mScrollEnabled = scrollEnabled;
-            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
-                View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 :
-                    mFocusPosition);
-                if (focusView != null) {
-                    scrollToView(focusView, true);
-                }
+            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+                    && mFocusPosition != NO_POSITION) {
+                scrollToSelection(mBaseGridView, mFocusPosition, true);
             }
         }
     }
@@ -2291,16 +2315,16 @@
     }
 
     boolean hasPreviousViewInSameRow(int pos) {
-        if (mGrid == null || pos == NO_POSITION) {
+        if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
             return false;
         }
-        if (mFirstVisiblePos > 0) {
+        if (mGrid.getFirstVisibleIndex() > 0) {
             return true;
         }
         final int focusedRow = mGrid.getLocation(pos).row;
         for (int i = getChildCount() - 1; i >= 0; i--) {
             int position = getPositionByIndex(i);
-            StaggeredGrid.Location loc = mGrid.getLocation(position);
+            Grid.Location loc = mGrid.getLocation(position);
             if (loc != null && loc.row == focusedRow) {
                 if (position < pos) {
                     return true;
@@ -2329,6 +2353,10 @@
                 // Move on secondary direction uses default addFocusables().
                 return false;
             }
+            if (mPendingMoveSmoothScroller != null) {
+                // don't find next focusable if has pending movement.
+                return true;
+            }
             final View focused = recyclerView.findFocus();
             final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
             // Add focusables of focused item.
@@ -2347,7 +2375,7 @@
                         continue;
                     }
                     int position = getPositionByIndex(index);
-                    StaggeredGrid.Location loc = mGrid.getLocation(position);
+                    Grid.Location loc = mGrid.getLocation(position);
                     if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
                         if (focusedPos == NO_POSITION ||
                                 (movement == NEXT_ITEM && position > focusedPos)
@@ -2397,6 +2425,16 @@
         return true;
     }
 
+    private boolean hasCreatedLastItem() {
+        int count = mState.getItemCount();
+        return count == 0 || findViewByPosition(count - 1) != null;
+    }
+
+    private boolean hasCreatedFirstItem() {
+        int count = mState.getItemCount();
+        return count == 0 || findViewByPosition(0) != null;
+    }
+
     @Override
     public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
             RecyclerView.State state) {
@@ -2404,50 +2442,61 @@
 
         View view = null;
         int movement = getMovement(direction);
+        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
+        // We still treat single-row (or TODO non-staggered) Grid special using position because:
+        // we know exactly if an item should be selected (based on index) *before* it is
+        // added to hierarchy. Child view can change layout when it is selected.
+        // Knowing this ahead can avoid a second layout after view is inserted into tree.
+        // We can reduce choppiness of vertical scrolling BrowseFragment where row view has
+        // different layout padding when it is selected.
+        // Multiple-rows Grid is different case:  we don't know if the item should be selected
+        // until we add it to hierarchy and measure it.  Grid algorithm choose a row to put
+        // the item based on the item size.  FocusSearch mechanism will rely which row the item
+        // is laid out then makes choice whether to select it.  This can cause a second layout
+        // if selected child has a different layout.
         if (mNumRows == 1) {
-            // for simple row, use LinearSmoothScroller to smooth animation.
-            // It will stay at a fixed cap speed in continuous scroll.
             if (movement == NEXT_ITEM) {
                 int newPos = mFocusPosition + mNumRows;
-                if (newPos < getItemCount()) {
-                    setSelectionSmooth(mBaseGridView, newPos);
+                if (newPos < getItemCount() && mScrollEnabled && getChildCount() > 0) {
+                    int lastChildPos = getPosition(getChildAt(getChildCount() - 1));
+                    if (newPos < lastChildPos + mNumRows * MAX_PENDING_MOVES) {
+                        setSelectionSmooth(mBaseGridView, newPos);
+                    }
                     view = focused;
                 } else {
-                    if (!mFocusOutEnd) {
+                    if (isScroll || !mFocusOutEnd) {
                         view = focused;
                     }
                 }
-            } else if (movement == PREV_ITEM){
+            } else if (movement == PREV_ITEM) {
                 int newPos = mFocusPosition - mNumRows;
-                if (newPos >= 0) {
-                    setSelectionSmooth(mBaseGridView, newPos);
+                if (newPos >= 0 && mScrollEnabled && getChildCount() > 0) {
+                    int firstChildPos = getPosition(getChildAt(0));
+                    if (newPos > firstChildPos - mNumRows * MAX_PENDING_MOVES) {
+                        setSelectionSmooth(mBaseGridView, newPos);
+                    }
                     view = focused;
                 } else {
-                    if (!mFocusOutFront) {
+                    if (isScroll || !mFocusOutFront) {
                         view = focused;
                     }
                 }
             }
         } else if (mNumRows > 1) {
-            // for possible staggered grid,  we need guarantee focus to same row/column.
-            // TODO: we may also use LinearSmoothScroller.
             saveContext(recycler, state);
-            final FocusFinder ff = FocusFinder.getInstance();
             if (movement == NEXT_ITEM) {
-                while (view == null && !appendOneVisibleItem()) {
-                    view = ff.findNextFocus(mBaseGridView, focused, direction);
+                if (isScroll || !mFocusOutEnd) {
+                    view = focused;
                 }
-            } else if (movement == PREV_ITEM){
-                while (view == null && !prependOneVisibleItem()) {
-                    view = ff.findNextFocus(mBaseGridView, focused, direction);
+                if (mScrollEnabled) {
+                    processPendingMovement(true);
                 }
-            }
-            if (view == null) {
-                // returning the same view to prevent focus lost when scrolling past the end of the list
-                if (movement == PREV_ITEM) {
-                    view = mFocusOutFront ? null : focused;
-                } else if (movement == NEXT_ITEM){
-                    view = mFocusOutEnd ? null : focused;
+            } else if (movement == PREV_ITEM) {
+                if (isScroll || !mFocusOutFront) {
+                    view = focused;
+                }
+                if (mScrollEnabled) {
+                    processPendingMovement(false);
                 }
             }
             leaveContext();
@@ -2525,10 +2574,10 @@
         if (mOrientation == HORIZONTAL) {
             switch(direction) {
                 case View.FOCUS_LEFT:
-                    movement = PREV_ITEM;
+                    movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
                     break;
                 case View.FOCUS_RIGHT:
-                    movement = NEXT_ITEM;
+                    movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
                     break;
                 case View.FOCUS_UP:
                     movement = PREV_ROW;
@@ -2540,10 +2589,10 @@
          } else if (mOrientation == VERTICAL) {
              switch(direction) {
                  case View.FOCUS_LEFT:
-                     movement = PREV_ROW;
+                     movement = (!mReverseFlowPrimary) ? PREV_ROW : NEXT_ROW;
                      break;
                  case View.FOCUS_RIGHT:
-                     movement = NEXT_ROW;
+                     movement = (!mReverseFlowPrimary) ? NEXT_ROW : PREV_ROW;
                      break;
                  case View.FOCUS_UP:
                      movement = PREV_ITEM;
@@ -2589,10 +2638,7 @@
 
     private void discardLayoutInfo() {
         mGrid = null;
-        mRows = null;
         mRowSizeSecondary = null;
-        mFirstVisiblePos = -1;
-        mLastVisiblePos = -1;
         mRowSecondarySizeRefresh = false;
     }
 
@@ -2668,6 +2714,13 @@
         return ss;
     }
 
+    void onChildRecycled(RecyclerView.ViewHolder holder) {
+        final int position = holder.getAdapterPosition();
+        if (position != NO_POSITION) {
+            mChildrenStates.saveOffscreenView(holder.itemView, position);
+        }
+    }
+
     @Override
     public void onRestoreInstanceState(Parcelable state) {
         if (!(state instanceof SavedState)) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
index a280f4f..d94913c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
@@ -22,23 +22,21 @@
 public class HeaderItem {
 
     private final long mId;
-    private final String mImageUri;
     private final String mName;
 
     /**
      * Create a header item.  All fields are optional.
      */
-    public HeaderItem(long id, String name, String imageUri) {
+    public HeaderItem(long id, String name) {
         mId = id;
         mName = name;
-        mImageUri = imageUri;
     }
 
     /**
-     * Create a header item.  All fields are optional.
+     * Create a header item.
      */
-    public HeaderItem(String name, String imageUri) {
-        this(NO_ID, name, imageUri);
+    public HeaderItem(String name) {
+        this(NO_ID, name);
     }
 
     /**
@@ -54,12 +52,4 @@
     public final String getName() {
         return mName;
     }
-
-    /**
-     * Returns the icon for this header item.
-     */
-    public final String getImageUri() {
-        return mImageUri;
-    }
-
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
index 0700995..425883a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
@@ -14,6 +14,7 @@
 package android.support.v17.leanback.widget;
 
 import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.MeasureSpec;
@@ -42,12 +43,18 @@
     protected void onViewSelected(View view) {
         int rightLimit = getParentViewGroup().getWidth() -
                 getParentViewGroup().getPaddingRight();
-        // measure the hover card width, if it's too large,  align hover card
-        // right edge with row view's right edge
+        int leftLimit = getParentViewGroup().getPaddingLeft();
+        // measure the hover card width; if it's too large, align hover card
+        // end edge with row view's end edge, otherwise align start edges.
         view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
         MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
-        if (mCardLeft + view.getMeasuredWidth() > rightLimit) {
+        boolean isRtl = ViewCompat.getLayoutDirection(view) == View.LAYOUT_DIRECTION_RTL;
+        if (!isRtl && mCardLeft + view.getMeasuredWidth() > rightLimit) {
             params.leftMargin = rightLimit  - view.getMeasuredWidth();
+        } else if (isRtl && mCardLeft < leftLimit) {
+            params.leftMargin = leftLimit;
+        } else if (isRtl) {
+            params.leftMargin = mCardRight - view.getMeasuredWidth();
         } else {
             params.leftMargin = mCardLeft;
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
index c4bb99a..d1545dd 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -37,6 +37,7 @@
     private TextView mContentView;
     private ImageView mBadgeImage;
     private ImageView mBadgeFadeMask;
+    private boolean mAttachedToWindow;
 
     public ImageCardView(Context context) {
         this(context, null);
@@ -111,7 +112,7 @@
         } else {
             mImageView.setVisibility(View.VISIBLE);
             if (fade) {
-                fadeIn(mImageView);
+                fadeIn();
             } else {
                 mImageView.animate().cancel();
                 mImageView.setAlpha(1f);
@@ -216,10 +217,12 @@
         return mBadgeImage.getDrawable();
     }
 
-    private void fadeIn(View v) {
-        v.setAlpha(0f);
-        v.animate().alpha(1f).setDuration(v.getContext().getResources().getInteger(
-                android.R.integer.config_shortAnimTime)).start();
+    private void fadeIn() {
+        mImageView.setAlpha(0f);
+        if (mAttachedToWindow) {
+            mImageView.animate().alpha(1f).setDuration(mImageView.getResources().getInteger(
+                    android.R.integer.config_shortAnimTime));
+        }
     }
 
     @Override
@@ -241,7 +244,17 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAttachedToWindow = true;
+        if (mImageView.getAlpha() == 0) {
+            fadeIn();
+        }
+    }
+
+    @Override
     protected void onDetachedFromWindow() {
+        mAttachedToWindow = false;
         mImageView.animate().cancel();
         mImageView.setAlpha(1f);
         super.onDetachedFromWindow();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
index 97f9091..dae01fc 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
@@ -221,6 +221,7 @@
             mPresenters.add(presenter);
             type = mPresenters.indexOf(presenter);
             if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
+            onAddPresenter(presenter, type);
             if (mAdapterListener != null) {
                 mAdapterListener.onAddPresenter(presenter, type);
             }
@@ -229,12 +230,48 @@
     }
 
     /**
+     * Called when presenter is added to Adapter.
+     */
+    protected void onAddPresenter(Presenter presenter, int type) {
+    }
+
+    /**
+     * Called when ViewHolder is created.
+     */
+    protected void onCreate(ViewHolder viewHolder) {
+    }
+
+    /**
+     * Called when ViewHolder has been bound to data.
+     */
+    protected void onBind(ViewHolder viewHolder) {
+    }
+
+    /**
+     * Called when ViewHolder has been unbound from data.
+     */
+    protected void onUnbind(ViewHolder viewHolder) {
+    }
+
+    /**
+     * Called when ViewHolder has been attached to window.
+     */
+    protected void onAttachedToWindow(ViewHolder viewHolder) {
+    }
+
+    /**
+     * Called when ViewHolder has been detached from window.
+     */
+    protected void onDetachedFromWindow(ViewHolder viewHolder) {
+    }
+
+    /**
      * {@link View.OnFocusChangeListener} that assigned in
      * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
      * {@link View.OnFocusChangeListener} after that.
      */
     @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
         Presenter presenter = mPresenters.get(viewType);
         Presenter.ViewHolder presenterVh;
@@ -248,6 +285,7 @@
             view = presenterVh.view;
         }
         ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
+        onCreate(viewHolder);
         if (mAdapterListener != null) {
             mAdapterListener.onCreate(viewHolder);
         }
@@ -267,33 +305,34 @@
     }
 
     @Override
-    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+    public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
         if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
         ViewHolder viewHolder = (ViewHolder) holder;
         viewHolder.mItem = mAdapter.get(position);
 
         viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
 
+        onBind(viewHolder);
         if (mAdapterListener != null) {
             mAdapterListener.onBind(viewHolder);
         }
     }
 
     @Override
-    public void onViewRecycled(RecyclerView.ViewHolder holder) {
+    public final void onViewRecycled(RecyclerView.ViewHolder holder) {
         ViewHolder viewHolder = (ViewHolder) holder;
         viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
-
-        viewHolder.mItem = null;
-
+        onUnbind(viewHolder);
         if (mAdapterListener != null) {
             mAdapterListener.onUnbind(viewHolder);
         }
+        viewHolder.mItem = null;
     }
 
     @Override
-    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
+    public final void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
         ViewHolder viewHolder = (ViewHolder) holder;
+        onAttachedToWindow(viewHolder);
         if (mAdapterListener != null) {
             mAdapterListener.onAttachedToWindow(viewHolder);
         }
@@ -301,9 +340,10 @@
     }
 
     @Override
-    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+    public final void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
         ViewHolder viewHolder = (ViewHolder) holder;
         viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
+        onDetachedFromWindow(viewHolder);
         if (mAdapterListener != null) {
             mAdapterListener.onDetachedFromWindow(viewHolder);
         }
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 a8deee4..e8a72c0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -22,6 +22,8 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 
+import java.util.HashMap;
+
 /**
  * ListRowPresenter renders {@link ListRow} using a
  * {@link HorizontalGridView} hosted in a {@link ListRowView}.
@@ -47,10 +49,12 @@
     private static final String TAG = "ListRowPresenter";
     private static final boolean DEBUG = false;
 
+    private static final int DEFAULT_RECYCLED_POOL_SIZE = 24;
+
     public static class ViewHolder extends RowPresenter.ViewHolder {
         final ListRowPresenter mListRowPresenter;
         final HorizontalGridView mGridView;
-        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        ItemBridgeAdapter mItemBridgeAdapter;
         final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
         final int mPaddingTop;
         final int mPaddingBottom;
@@ -80,6 +84,58 @@
         }
     }
 
+    class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter {
+        ListRowPresenter.ViewHolder mRowViewHolder;
+
+        ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
+            mRowViewHolder = rowViewHolder;
+        }
+
+        @Override
+        public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
+            // Only when having an OnItemClickListner, we will attach the OnClickListener.
+            if (getOnItemClickedListener() != null || 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 (getOnItemClickedListener() != null) {
+                            getOnItemClickedListener().onItemClicked(ibh.mItem,
+                                    (ListRow) mRowViewHolder.mRow);
+                        }
+                        if (getOnItemViewClickedListener() != null) {
+                            getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
+                                    ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow);
+                        }
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
+            if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
+                viewHolder.mHolder.view.setOnClickListener(null);
+            }
+        }
+
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+            if (viewHolder.itemView instanceof ShadowOverlayContainer) {
+                int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor();
+                ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
+            }
+            mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
+        }
+
+        @Override
+        public void onAddPresenter(Presenter presenter, int type) {
+            mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
+                    type, getRecycledPoolSize(presenter));
+        }
+    }
+
     private int mRowHeight;
     private int mExpandedRowHeight;
     private PresenterSelector mHoverCardPresenterSelector;
@@ -87,6 +143,7 @@
     private boolean mShadowEnabled = true;
     private int mBrowseRowsFadingEdgeLength = -1;
     private boolean mRoundedCornersEnabled = true;
+    private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
 
     private static int sSelectedRowTopPadding;
     private static int sExpandedSelectedRowTopPadding;
@@ -106,10 +163,14 @@
      * @param zoomFactor Controls the zoom factor used when an item view is focused. One of
      *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
      *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
+     *         {@link FocusHighlight#ZOOM_FACTOR_XSMALL},
      *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
      *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
      */
     public ListRowPresenter(int zoomFactor) {
+        if (!FocusHighlightHelper.isValidZoomIndex(zoomFactor)) {
+            throw new IllegalArgumentException("Unhandled zoom factor");
+        }
         mZoomFactor = zoomFactor;
     }
 
@@ -178,16 +239,13 @@
     protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
         super.initializeRowViewHolder(holder);
         final ViewHolder rowViewHolder = (ViewHolder) holder;
+        rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
         if (needsDefaultListSelectEffect() || needsDefaultShadow()
                 || areChildRoundedCornersEnabled()) {
             rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
         }
         if (needsDefaultListSelectEffect()) {
             ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
-            ((ViewGroup) rowViewHolder.view).setClipChildren(false);
-            if (rowViewHolder.mContainerViewHolder != null) {
-                ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false);
-            }
         }
         FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
                 mZoomFactor, false);
@@ -199,52 +257,6 @@
                 selectChildView(rowViewHolder, view);
             }
         });
-        rowViewHolder.mItemBridgeAdapter.setAdapterListener(
-                new ItemBridgeAdapter.AdapterListener() {
-            @Override
-            public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
-                // Only when having an OnItemClickListner, we will attach the OnClickListener.
-                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
-                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
-                        @Override
-                        public void onClick(View v) {
-                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
-                                    rowViewHolder.mGridView
-                                            .getChildViewHolder(viewHolder.itemView);
-                            if (getOnItemClickedListener() != null) {
-                                getOnItemClickedListener().onItemClicked(ibh.mItem,
-                                        (ListRow) rowViewHolder.mRow);
-                            }
-                            if (getOnItemViewClickedListener() != null) {
-                                getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
-                                        ibh.mItem, rowViewHolder, (ListRow) rowViewHolder.mRow);
-                            }
-                        }
-                    });
-                }
-            }
-
-            @Override
-            public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
-                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
-                    viewHolder.mHolder.view.setOnClickListener(null);
-                }
-            }
-
-            @Override
-            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
-                if (viewHolder.itemView instanceof ShadowOverlayContainer) {
-                    int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor();
-                    ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
-                }
-                viewHolder.itemView.setActivated(rowViewHolder.mExpanded);
-            }
-
-            @Override
-            public void onAddPresenter(Presenter presenter, int type) {
-                rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(type, 24);
-            }
-        });
     }
 
     final boolean needsDefaultListSelectEffect() {
@@ -252,6 +264,21 @@
     }
 
     /**
+     * Sets the recycled pool size for the given presenter.
+     */
+    public void setRecycledPoolSize(Presenter presenter, int size) {
+        mRecycledPoolSize.put(presenter, size);
+    }
+
+    /**
+     * Returns the recycled pool size for the given presenter.
+     */
+    public int getRecycledPoolSize(Presenter presenter) {
+        return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) :
+                DEFAULT_RECYCLED_POOL_SIZE;
+    }
+
+    /**
      * Set {@link PresenterSelector} used for showing a select object in a hover card.
      */
     public final void setHoverCardPresenterSelector(PresenterSelector selector) {
@@ -269,12 +296,24 @@
      * Perform operations when a child of horizontal grid view is selected.
      */
     private void selectChildView(ViewHolder rowViewHolder, View view) {
-        ItemBridgeAdapter.ViewHolder ibh = null;
         if (view != null) {
-            ibh = (ItemBridgeAdapter.ViewHolder)
-                    rowViewHolder.mGridView.getChildViewHolder(view);
-        }
-        if (view == null) {
+            if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
+                ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
+                        rowViewHolder.mGridView.getChildViewHolder(view);
+
+                if (mHoverCardPresenterSelector != null) {
+                    rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
+                            ibh.mItem);
+                }
+                if (getOnItemViewSelectedListener() != null) {
+                    getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem,
+                            rowViewHolder, rowViewHolder.mRow);
+                }
+                if (getOnItemSelectedListener() != null) {
+                    getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
+                }
+            }
+        } else {
             if (mHoverCardPresenterSelector != null) {
                 rowViewHolder.mHoverCardViewSwitcher.unselect();
             }
@@ -285,18 +324,6 @@
             if (getOnItemSelectedListener() != null) {
                 getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
             }
-        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
-            if (mHoverCardPresenterSelector != null) {
-                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
-                        ibh.mItem);
-            }
-            if (getOnItemViewSelectedListener() != null) {
-                getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem,
-                        rowViewHolder, rowViewHolder.mRow);
-            }
-            if (getOnItemSelectedListener() != null) {
-                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
-            }
         }
     }
 
@@ -354,6 +381,31 @@
         return new ViewHolder(rowView, rowView.getGridView(), this);
     }
 
+    /**
+     * Dispatch item selected event using current selected item in the {@link HorizontalGridView}.
+     * The method should only be called from onRowViewSelected().
+     */
+    @Override
+    protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) {
+        ViewHolder vh = (ViewHolder)holder;
+        ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder)
+                vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition());
+        if (itemViewHolder == null) {
+            super.dispatchItemSelectedListener(holder, selected);
+            return;
+        }
+
+        if (selected) {
+            if (getOnItemViewSelectedListener() != null) {
+                getOnItemViewSelectedListener().onItemSelected(
+                        itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow());
+            }
+            if (getOnItemSelectedListener() != null) {
+                getOnItemSelectedListener().onItemSelected(itemViewHolder.mItem, vh.getRow());
+            }
+        }
+    }
+
     @Override
     protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
         super.onRowViewSelected(holder, selected);
@@ -537,4 +589,11 @@
         vh.mGridView.setScrollEnabled(!freeze);
     }
 
+    @Override
+    public void setEntranceTransitionState(RowPresenter.ViewHolder holder,
+            boolean afterEntrance) {
+        super.setEntranceTransitionState(holder, afterEntrance);
+        ((ViewHolder) holder).mGridView.setChildrenVisibility(
+                afterEntrance? View.VISIBLE : View.INVISIBLE);
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java
new file mode 100644
index 0000000..158e719
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.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.v17.leanback.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Interface definition for a callback to be invoked when a child of this
+ * ViewGroup has been laid out.
+ */
+public interface OnChildLaidOutListener {
+    /**
+     * Callback method to be invoked when a child of this ViewGroup has been
+     * added to view hierarchy laid out.
+     *
+     * @param parent The ViewGroup where the layout happened.
+     * @param view The view within the ViewGroup that is selected, or null if no
+     *        view is selected.
+     * @param position The position of the view in the adapter.
+     * @param id The id of the child.
+     */
+    void onChildLaidOut(ViewGroup parent, View view, int position, long id);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
index 03648c6..5addaf5 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -49,6 +49,7 @@
         private int mIndex;
         private Drawable[] mDrawables;
         private String[] mLabels;
+        private String[] mLabels2;
 
         /**
          * Constructor
@@ -67,37 +68,64 @@
             setIndex(0);
         }
 
+        /**
+         * Sets the array of strings used as labels.  The size of the array defines the range
+         * of valid indices for this action.  The labels are used to define the accessibility
+         * content description unless secondary labels are provided.
+         */
         public void setLabels(String[] labels) {
             mLabels = labels;
             setIndex(0);
         }
 
         /**
-         * Returns the number of drawables.
+         * Sets the array of strings used as secondary labels.  These labels are used
+         * in place of the primary labels for accessibility content description only.
          */
-        public int getNumberOfDrawables() {
-            return mDrawables.length;
+        public void setSecondaryLabels(String[] labels) {
+            mLabels2 = labels;
+            setIndex(0);
+        }
+
+        /**
+         * Returns the number of actions.
+         */
+        public int getActionCount() {
+            if (mDrawables != null) {
+                return mDrawables.length;
+            }
+            if (mLabels != null) {
+                return mLabels.length;
+            }
+            return 0;
         }
 
         /**
          * Returns the drawable at the given index.
          */
         public Drawable getDrawable(int index) {
-            return mDrawables[index];
+            return mDrawables == null ? null : mDrawables[index];
         }
 
         /**
          * Returns the label at the given index.
          */
         public String getLabel(int index) {
-            return mLabels[index];
+            return mLabels == null ? null : mLabels[index];
+        }
+
+        /**
+         * Returns the secondary label at the given index.
+         */
+        public String getSecondaryLabel(int index) {
+            return mLabels2 == null ? null : mLabels2[index];
         }
 
         /**
          * Increments the index, wrapping to zero once the end is reached.
          */
         public void nextIndex() {
-            setIndex(mIndex < mDrawables.length - 1 ? mIndex + 1 : 0);
+            setIndex(mIndex < getActionCount() - 1 ? mIndex + 1 : 0);
         }
 
         /**
@@ -105,10 +133,15 @@
          */
         public void setIndex(int index) {
             mIndex = index;
-            setIcon(mDrawables[mIndex]);
+            if (mDrawables != null) {
+                setIcon(mDrawables[mIndex]);
+            }
             if (mLabels != null) {
                 setLabel1(mLabels[mIndex]);
             }
+            if (mLabels2 != null) {
+                setLabel2(mLabels2[mIndex]);
+            }
         }
 
         /**
@@ -156,32 +189,92 @@
     /**
      * An action displaying an icon for fast forward.
      */
-    public static class FastForwardAction extends Action {
+    public static class FastForwardAction extends MultiAction {
         /**
          * Constructor
          * @param context Context used for loading resources.
          */
         public FastForwardAction(Context context) {
+            this(context, 1);
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         * @param numSpeeds Number of supported fast forward speeds.
+         */
+        public FastForwardAction(Context context, int numSpeeds) {
             super(R.id.lb_control_fast_forward);
-            setIcon(getStyledDrawable(context,
-                    R.styleable.lbPlaybackControlsActionIcons_fast_forward));
-            setLabel1(context.getString(R.string.lb_playback_controls_fast_forward));
+
+            if (numSpeeds < 1) {
+                throw new IllegalArgumentException("numSpeeds must be > 0");
+            }
+            Drawable[] drawables = new Drawable[numSpeeds];
+            drawables[0] = getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_fast_forward);
+            setDrawables(drawables);
+
+            String[] labels = new String[getActionCount()];
+            labels[0] = context.getString(R.string.lb_playback_controls_fast_forward);
+
+            String[] labels2 = new String[getActionCount()];
+            labels2[0] = labels[0];
+
+            for (int i = 1; i < numSpeeds; i++) {
+                int multiplier = i + 1;
+                labels[i] = context.getResources().getString(
+                        R.string.lb_control_display_fast_forward_multiplier, multiplier);
+                labels2[i] = context.getResources().getString(
+                        R.string.lb_playback_controls_fast_forward_multiplier, multiplier);
+            }
+            setLabels(labels);
+            setSecondaryLabels(labels2);
         }
     }
 
     /**
      * An action displaying an icon for rewind.
      */
-    public static class RewindAction extends Action {
+    public static class RewindAction extends MultiAction {
         /**
          * Constructor
          * @param context Context used for loading resources.
          */
         public RewindAction(Context context) {
+            this(context, 1);
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         * @param numSpeeds Number of supported fast forward speeds.
+         */
+        public RewindAction(Context context, int numSpeeds) {
             super(R.id.lb_control_fast_rewind);
-            setIcon(getStyledDrawable(context,
-                    R.styleable.lbPlaybackControlsActionIcons_rewind));
-            setLabel1(context.getString(R.string.lb_playback_controls_rewind));
+
+            if (numSpeeds < 1) {
+                throw new IllegalArgumentException("numSpeeds must be > 0");
+            }
+            Drawable[] drawables = new Drawable[numSpeeds];
+            drawables[0] = getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_rewind);
+            setDrawables(drawables);
+
+            String[] labels = new String[getActionCount()];
+            labels[0] = context.getString(R.string.lb_playback_controls_rewind);
+
+            String[] labels2 = new String[getActionCount()];
+            labels2[0] = labels[0];
+
+            for (int i = 1; i < numSpeeds; i++) {
+                int multiplier = i + 1;
+                labels[i] = labels[i] = context.getResources().getString(
+                        R.string.lb_control_display_rewind_multiplier, multiplier);
+                labels2[i] = context.getResources().getString(
+                        R.string.lb_playback_controls_rewind_multiplier, multiplier);
+            }
+            setLabels(labels);
+            setSecondaryLabels(labels2);
         }
     }
 
@@ -267,7 +360,7 @@
             super(R.id.lb_control_thumbs_up, context,
                     R.styleable.lbPlaybackControlsActionIcons_thumb_up,
                     R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
-            String[] labels = new String[getNumberOfDrawables()];
+            String[] labels = new String[getActionCount()];
             labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_up);
             labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_up_outline);
             setLabels(labels);
@@ -282,7 +375,7 @@
             super(R.id.lb_control_thumbs_down, context,
                     R.styleable.lbPlaybackControlsActionIcons_thumb_down,
                     R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
-            String[] labels = new String[getNumberOfDrawables()];
+            String[] labels = new String[getActionCount()];
             labels[SOLID] = context.getString(R.string.lb_playback_controls_thumb_down);
             labels[OUTLINE] = context.getString(R.string.lb_playback_controls_thumb_down_outline);
             setLabels(labels);
@@ -600,6 +693,8 @@
 
     /**
      * Sets the current time in milliseconds for the playback controls row.
+     * If this row is bound to a view, the view will automatically
+     * be updated to reflect the new value.
      */
     public void setCurrentTime(int ms) {
         if (mCurrentTimeMs != ms) {
@@ -617,6 +712,8 @@
 
     /**
      * Sets the buffered progress for the playback controls row.
+     * If this row is bound to a view, the view will automatically
+     * be updated to reflect the new value.
      */
     public void setBufferedProgress(int ms) {
         if (mBufferedProgressMs != ms) {
@@ -640,14 +737,14 @@
     /**
      * Sets a listener to be called when the playback state changes.
      */
-    public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
+    void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
         mListener = listener;
     }
 
     /**
      * Returns the playback state listener.
      */
-    public OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
+    OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
         return mListener;
     }
 
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 be55a40..9c62a1b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -116,6 +116,9 @@
             ObjectAdapter adapter = primary ?
                     ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() :
                             ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
+            if (adapter == null) {
+                return null;
+            }
             if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
                 ControlButtonPresenterSelector selector =
                         (ControlButtonPresenterSelector) adapter.getPresenterSelector();
@@ -361,6 +364,7 @@
 
         MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
         if (row.getImageDrawable() == null || row.getItem() == null) {
+            vh.mImageView.setImageDrawable(null);
             vh.setBackground(vh.mControlsDock);
             lp.setMarginStart(0);
             lp.setMarginEnd(0);
@@ -413,4 +417,22 @@
             ((ViewHolder) vh).dispatchItemSelection();
         }
     }
+
+    @Override
+    protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
+        super.onRowViewAttachedToWindow(vh);
+        if (mDescriptionPresenter != null) {
+            mDescriptionPresenter.onViewAttachedToWindow(
+                    ((ViewHolder) vh).mDescriptionViewHolder);
+        }
+    }
+
+    @Override
+    protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
+        super.onRowViewDetachedFromWindow(vh);
+        if (mDescriptionPresenter != null) {
+            mDescriptionPresenter.onViewDetachedFromWindow(
+                    ((ViewHolder) vh).mDescriptionViewHolder);
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java
new file mode 100644
index 0000000..05270f0a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java
@@ -0,0 +1,46 @@
+/*
+ * 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.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * A LinearLayout that preserves the focused child view.
+ * @hide
+ */
+class PlaybackControlsRowView extends LinearLayout {
+    public PlaybackControlsRowView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public PlaybackControlsRowView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        final View focused = findFocus();
+        if (focused != null && focused.requestFocus(direction, previouslyFocusedRect)) {
+            return true;
+        }
+        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
index 9a2b0bf..7e1ecb0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -101,7 +101,7 @@
      *
      * <p>Becoming detached from the window is not necessarily a permanent condition;
      * the consumer of an presenter's views may choose to cache views offscreen while they
-     * are not visible, attaching an detaching them as appropriate.</p>
+     * are not visible, attaching and detaching them as appropriate.</p>
      *
      * Any view property animations should be cancelled here or the view may fail
      * to be recycled.
@@ -117,7 +117,7 @@
      * Utility method for removing all running animations on a view.
      */
     protected static void cancelAnimationsRecursive(View view) {
-        if (view.hasTransientState()) {
+        if (view != null && view.hasTransientState()) {
             view.animate().cancel();
             if (view instanceof ViewGroup) {
                 final int count = ((ViewGroup) view).getChildCount();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java b/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
new file mode 100644
index 0000000..45aba9f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import android.support.v17.leanback.R;
+
+/**
+ * <p>A {@link android.widget.TextView} that adjusts text size automatically in response
+ * to certain trigger conditions, such as text that wraps over multiple lines.</p>
+ * @hide
+ */
+class ResizingTextView extends TextView {
+
+    /**
+     * Trigger text resize when text flows into the last line of a multi-line text view.
+     */
+    public static final int TRIGGER_MAX_LINES = 0x01;
+
+    private int mTriggerConditions; // Union of trigger conditions
+    private int mResizedTextSize;
+    // Note: Maintaining line spacing turned out not to be useful, and will be removed in
+    // the next round of design for this class (b/18736630). For now it simply defaults to false.
+    private boolean mMaintainLineSpacing;
+    private int mResizedPaddingAdjustmentTop;
+    private int mResizedPaddingAdjustmentBottom;
+
+    private boolean mIsResized = false;
+    // Remember default properties in case we need to restore them
+    private boolean mDefaultsInitialized = false;
+    private int mDefaultTextSize;
+    private float mDefaultLineSpacingExtra;
+    private int mDefaultPaddingTop;
+    private int mDefaultPaddingBottom;
+
+    public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(ctx, attrs, defStyleAttr);
+        TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.lbResizingTextView,
+                defStyleAttr, defStyleRes);
+
+        try {
+            mTriggerConditions = a.getInt(
+                    R.styleable.lbResizingTextView_resizeTrigger, TRIGGER_MAX_LINES);
+            mResizedTextSize = a.getDimensionPixelSize(
+                    R.styleable.lbResizingTextView_resizedTextSize, -1);
+            mMaintainLineSpacing = a.getBoolean(
+                    R.styleable.lbResizingTextView_maintainLineSpacing, false);
+            mResizedPaddingAdjustmentTop = a.getDimensionPixelOffset(
+                    R.styleable.lbResizingTextView_resizedPaddingAdjustmentTop, 0);
+            mResizedPaddingAdjustmentBottom = a.getDimensionPixelOffset(
+                    R.styleable.lbResizingTextView_resizedPaddingAdjustmentBottom, 0);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    public ResizingTextView(Context ctx, AttributeSet attrs, int defStyleAttr) {
+        this(ctx, attrs, defStyleAttr, 0);
+    }
+
+    public ResizingTextView(Context ctx, AttributeSet attrs) {
+        // TODO We should define our own style that inherits from TextViewStyle, to set defaults
+        // for new styleables,  We then pass the appropriate R.attr up the constructor chain here.
+        this(ctx, attrs, android.R.attr.textViewStyle);
+    }
+
+    public ResizingTextView(Context ctx) {
+        this(ctx, null);
+    }
+
+    /**
+     * @return the trigger conditions used to determine whether resize occurs
+     */
+    public int getTriggerConditions() {
+        return mTriggerConditions;
+    }
+
+    /**
+     * Set the trigger conditions used to determine whether resize occurs. Pass
+     * a union of trigger condition constants, such as {@link ResizingTextView#TRIGGER_MAX_LINES}.
+     *
+     * @param conditions A union of trigger condition constants
+     */
+    public void setTriggerConditions(int conditions) {
+        if (mTriggerConditions != conditions) {
+            mTriggerConditions = conditions;
+            // Always request a layout when trigger conditions change
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return the resized text size
+     */
+    public int getResizedTextSize() {
+        return mResizedTextSize;
+    }
+
+    /**
+     * Set the text size for resized text.
+     *
+     * @param size The text size for resized text
+     */
+    public void setResizedTextSize(int size) {
+        if (mResizedTextSize != size) {
+            mResizedTextSize = size;
+            resizeParamsChanged();
+        }
+    }
+
+    /**
+     * @return whether or not to maintain line spacing when resizing text.
+     * The default is true.
+     */
+    public boolean getMaintainLineSpacing() {
+        return mMaintainLineSpacing;
+    }
+
+    /**
+     * Set whether or not to maintain line spacing when resizing text.
+     * The default is true.
+     *
+     * @param maintain Whether or not to maintain line spacing
+     */
+    public void setMaintainLineSpacing(boolean maintain) {
+        if (mMaintainLineSpacing != maintain) {
+            mMaintainLineSpacing = maintain;
+            resizeParamsChanged();
+        }
+    }
+
+    /**
+     * @return desired adjustment to top padding for resized text
+     */
+    public int getResizedPaddingAdjustmentTop() {
+        return mResizedPaddingAdjustmentTop;
+    }
+
+    /**
+     * Set the desired adjustment to top padding for resized text.
+     *
+     * @param adjustment The adjustment to top padding, in pixels
+     */
+    public void setResizedPaddingAdjustmentTop(int adjustment) {
+        if (mResizedPaddingAdjustmentTop != adjustment) {
+            mResizedPaddingAdjustmentTop = adjustment;
+            resizeParamsChanged();
+        }
+    }
+
+    /**
+     * @return desired adjustment to bottom padding for resized text
+     */
+    public int getResizedPaddingAdjustmentBottom() {
+        return mResizedPaddingAdjustmentBottom;
+    }
+
+    /**
+     * Set the desired adjustment to bottom padding for resized text.
+     *
+     * @param adjustment The adjustment to bottom padding, in pixels
+     */
+    public void setResizedPaddingAdjustmentBottom(int adjustment) {
+        if (mResizedPaddingAdjustmentBottom != adjustment) {
+            mResizedPaddingAdjustmentBottom = adjustment;
+            resizeParamsChanged();
+        }
+    }
+
+    private void resizeParamsChanged() {
+        // If we're not resized, then changing resize parameters doesn't
+        // affect layout, so don't bother requesting.
+        if (mIsResized) {
+            requestLayout();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!mDefaultsInitialized) {
+            mDefaultTextSize = (int) getTextSize();
+            mDefaultLineSpacingExtra = getLineSpacingExtra();
+            mDefaultPaddingTop = getPaddingTop();
+            mDefaultPaddingBottom = getPaddingBottom();
+            mDefaultsInitialized = true;
+        }
+
+        // Always try first to measure with defaults. Otherwise, we may think we can get away
+        // with larger text sizes later when we actually can't.
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
+        setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
+        setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        boolean resizeText = false;
+
+        final Layout layout = getLayout();
+        if (layout != null) {
+            if ((mTriggerConditions & TRIGGER_MAX_LINES) > 0) {
+                final int lineCount = layout.getLineCount();
+                final int maxLines = getMaxLines();
+                if (maxLines > 1) {
+                    resizeText = lineCount == maxLines;
+                }
+            }
+        }
+
+        final int currentSizePx = (int) getTextSize();
+        boolean remeasure = false;
+        if (resizeText) {
+            if (mResizedTextSize != -1 && currentSizePx != mResizedTextSize) {
+                setTextSize(TypedValue.COMPLEX_UNIT_PX, mResizedTextSize);
+                remeasure = true;
+            }
+            // Check for other desired adjustments in addition to the text size
+            final float targetLineSpacingExtra = mDefaultLineSpacingExtra +
+                    mDefaultTextSize - mResizedTextSize;
+            if (mMaintainLineSpacing && getLineSpacingExtra() != targetLineSpacingExtra) {
+                setLineSpacing(targetLineSpacingExtra, getLineSpacingMultiplier());
+                remeasure = true;
+            }
+            final int paddingTop = mDefaultPaddingTop + mResizedPaddingAdjustmentTop;
+            final int paddingBottom = mDefaultPaddingBottom + mResizedPaddingAdjustmentBottom;
+            if (getPaddingTop() != paddingTop || getPaddingBottom() != paddingBottom) {
+                setPaddingTopAndBottom(paddingTop, paddingBottom);
+                remeasure = true;
+            }
+        } else {
+            // Use default size, line spacing, and padding
+            if (mResizedTextSize != -1 && currentSizePx != mDefaultTextSize) {
+                setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
+                remeasure = true;
+            }
+            if (mMaintainLineSpacing && getLineSpacingExtra() != mDefaultLineSpacingExtra) {
+                setLineSpacing(mDefaultLineSpacingExtra, getLineSpacingMultiplier());
+                remeasure = true;
+            }
+            if (getPaddingTop() != mDefaultPaddingTop ||
+                    getPaddingBottom() != mDefaultPaddingBottom) {
+                setPaddingTopAndBottom(mDefaultPaddingTop, mDefaultPaddingBottom);
+                remeasure = true;
+            }
+        }
+        mIsResized = resizeText;
+        if (remeasure) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private void setPaddingTopAndBottom(int paddingTop, int paddingBottom) {
+        if (isPaddingRelative()) {
+            setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), paddingBottom);
+        } else {
+            setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), paddingBottom);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
index 886bf89..a56aea6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -29,6 +29,7 @@
 
     private final int mLayoutResourceId;
     private final Paint mFontMeasurePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private boolean mNullItemVisibilityGone;
 
     public RowHeaderPresenter() {
         this(R.layout.lb_row_header);
@@ -41,6 +42,20 @@
         mLayoutResourceId = layoutResourceId;
     }
 
+    /**
+     * Optionally sets the view visibility to {@link View#GONE} when bound to null.
+     */
+    public void setNullItemVisibilityGone(boolean nullItemVisibilityGone) {
+        mNullItemVisibilityGone = nullItemVisibilityGone;
+    }
+
+    /**
+     * Returns true if the view visibility is set to {@link View#GONE} when bound to null.
+     */
+    public boolean isNullItemVisibilityGone() {
+        return mNullItemVisibilityGone;
+    }
+
     public static class ViewHolder extends Presenter.ViewHolder {
         float mSelectLevel;
         int mOriginalTextColor;
@@ -69,18 +84,21 @@
     @Override
     public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
         setSelectLevel((ViewHolder) viewHolder, 0);
-        Row rowItem = (Row) item;
-        if (rowItem != null) {
-            HeaderItem headerItem = rowItem.getHeaderItem();
-            if (headerItem != null) {
-                String text = headerItem.getName();
-                ((RowHeaderView) viewHolder.view).setText(text);
+        HeaderItem headerItem = item == null ? null : ((Row) item).getHeaderItem();
+        if (headerItem == null) {
+            ((RowHeaderView) viewHolder.view).setText(null);
+            if (mNullItemVisibilityGone) {
+                viewHolder.view.setVisibility(View.GONE);
             }
+        } else {
+            viewHolder.view.setVisibility(View.VISIBLE);
+            ((RowHeaderView) viewHolder.view).setText(headerItem.getName());
         }
     }
 
     @Override
     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        ((RowHeaderView) viewHolder.view).setText(null);
     }
 
     public final void setSelectLevel(ViewHolder holder, float selectLevel) {
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 1c7ed3d..781d292 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -35,18 +35,40 @@
  * RowPresenter receives calls from its parent (typically a Fragment) when:
  * <ul>
  * <li>
- * A Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
+ * A row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
  * is triggered immediately when there is a row selection change before the selection
- * animation is started.
+ * animation is started.  Selected status may control activated status of the row (see
+ * "Activated status" below).
  * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}.
  * </li>
  * <li>
- * A Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}.
+ * A row is expanded to full height via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}
+ * when BrowseFragment hides fast lane on the left.
  * The event is triggered immediately before the expand animation is started.
+ * Row title is shown when row is expanded.  Expanded status may control activated status
+ * of the row (see "Activated status" below).
  * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}.
  * </li>
  * </ul>
  *
+ * <h3>Activated status</h3>
+ * The activated status of a row is applied to the row view and it's children via
+ * {@link View#setActivated(boolean)}.
+ * The activated status is typically used to control {@link BaseCardView} info region visibility.
+ * The row's activated status can be controlled by selected status and/or expanded status.
+ * Call {@link #setSyncActivatePolicy(int)} and choose one of the four policies:
+ * <ul>
+ * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED} Activated status is synced with row expanded status</li>
+ * <li>{@link #SYNC_ACTIVATED_TO_SELECTED} Activated status is synced with row selected status</li>
+ * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED} Activated status is set to true
+ *     when both expanded and selected status are true</li>
+ * <li>{@link #SYNC_ACTIVATED_CUSTOM} Activated status is not controlled by selected status
+ *     or expanded status, application can control activated status by its own.
+ *     Application should call {@link RowPresenter.ViewHolder#setActivated(boolean)} to change
+ *     activated status of row view.
+ * </li>
+ * </ul>
+ *
  * <h3>User events</h3>
  * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}.
  * If a subclass wants to add its own {@link View.OnFocusChangeListener} or
@@ -72,6 +94,27 @@
  */
 public abstract class RowPresenter extends Presenter {
 
+    /**
+     * Don't synchronize row view activated status with selected status or expanded status,
+     * application will do its own through {@link RowPresenter.ViewHolder#setActivated(boolean)}.
+     */
+    public static final int SYNC_ACTIVATED_CUSTOM = 0;
+
+    /**
+     * Synchronizes row view's activated status to expand status of the row view holder.
+     */
+    public static final int SYNC_ACTIVATED_TO_EXPANDED = 1;
+
+    /**
+     * Synchronizes row view's activated status to selected status of the row view holder.
+     */
+    public static final int SYNC_ACTIVATED_TO_SELECTED = 2;
+
+    /**
+     * Sets the row view's activated status to true when both expand and selected are true.
+     */
+    public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3;
+
     static class ContainerViewHolder extends Presenter.ViewHolder {
         /**
          * wrapped row view holder
@@ -93,9 +136,14 @@
      * A view holder for a {@link Row}.
      */
     public static class ViewHolder extends Presenter.ViewHolder {
+        private static final int ACTIVATED_NOT_ASSIGNED = 0;
+        private static final int ACTIVATED = 1;
+        private static final int NOT_ACTIVATED = 2;
+
         ContainerViewHolder mContainerViewHolder;
         RowHeaderPresenter.ViewHolder mHeaderViewHolder;
         Row mRow;
+        int mActivated = ACTIVATED_NOT_ASSIGNED;
         boolean mSelected;
         boolean mExpanded;
         boolean mInitialzed;
@@ -150,6 +198,34 @@
         public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
             return mHeaderViewHolder;
         }
+
+        /**
+         * Sets the row view's activated status.  The status will be applied to children through
+         * {@link #syncActivatedStatus(View)}.  Application should only call this function
+         * when {@link RowPresenter#getSyncActivatePolicy()} is
+         * {@link RowPresenter#SYNC_ACTIVATED_CUSTOM}; otherwise the value will
+         * be overwritten when expanded or selected status changes.
+         */
+        public final void setActivated(boolean activated) {
+            mActivated = activated ? ACTIVATED : NOT_ACTIVATED;
+        }
+
+        /**
+         * Synchronizes the activated status of view to the last value passed through
+         * {@link RowPresenter.ViewHolder#setActivated(boolean)}. No operation if
+         * {@link RowPresenter.ViewHolder#setActivated(boolean)} is never called.  Normally
+         * application does not need to call this method,  {@link ListRowPresenter} automatically
+         * calls this method when a child is attached to list row.   However if
+         * application writes its own custom RowPresenter, it should call this method
+         * when attaches a child to the row view.
+         */
+        public final void syncActivatedStatus(View view) {
+            if (mActivated == ACTIVATED) {
+                view.setActivated(true);
+            } else if (mActivated == NOT_ACTIVATED) {
+                view.setActivated(false);
+            }
+        }
     }
 
     private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
@@ -159,6 +235,14 @@
     private OnItemViewClickedListener mOnItemViewClickedListener;
 
     boolean mSelectEffectEnabled = true;
+    int mSyncActivatePolicy = SYNC_ACTIVATED_TO_EXPANDED;
+
+    /**
+     * Constructs a RowPresenter.
+     */
+    public RowPresenter() {
+        mHeaderPresenter.setNullItemVisibilityGone(true);
+    }
 
     @Override
     public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
@@ -200,6 +284,13 @@
      */
     protected void initializeRowViewHolder(ViewHolder vh) {
         vh.mInitialzed = true;
+        // set clip children to false for slide transition
+        if (vh.view instanceof ViewGroup) {
+            ((ViewGroup) vh.view).setClipChildren(false);
+        }
+        if (vh.mContainerViewHolder != null) {
+            ((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
+        }
     }
 
     /**
@@ -262,15 +353,57 @@
      */
     protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
         updateHeaderViewVisibility(vh);
-        vh.view.setActivated(expanded);
+        updateActivateStatus(vh, vh.view);
     }
 
     /**
-     * Subclass may override this to respond to selected state changes of a Row.
-     * Subclass may make visual changes to Row view but must not create
-     * animation on the Row view.
+     * Update view's activate status according to {@link #getSyncActivatePolicy()} and the
+     * selected status and expanded status of the RowPresenter ViewHolder.
      */
-    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
+    private void updateActivateStatus(ViewHolder vh, View view) {
+        switch (mSyncActivatePolicy) {
+            case SYNC_ACTIVATED_TO_EXPANDED:
+                vh.setActivated(vh.isExpanded());
+                break;
+            case SYNC_ACTIVATED_TO_SELECTED:
+                vh.setActivated(vh.isSelected());
+                break;
+            case SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED:
+                vh.setActivated(vh.isExpanded() && vh.isSelected());
+                break;
+        }
+        vh.syncActivatedStatus(view);
+    }
+
+    /**
+     * Sets policy of updating row view activated status.  Can be one of:
+     * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
+     * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
+     * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
+     * <li> {@link #SYNC_ACTIVATED_CUSTOM}
+     */
+    public final void setSyncActivatePolicy(int syncActivatePolicy) {
+        mSyncActivatePolicy = syncActivatePolicy;
+    }
+
+    /**
+     * Returns policy of updating row view activated status.  Can be one of:
+     * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
+     * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
+     * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
+     * <li> {@link #SYNC_ACTIVATED_CUSTOM}
+     */
+    public final int getSyncActivatePolicy() {
+        return mSyncActivatePolicy;
+    }
+
+
+    /**
+     * The method is only called from onRowViewSelecetd().
+     * Default behavior is signaling row selected events with null item. Subclass of RowPresenter
+     * having child items should override this method and dispatch events with item information.
+     */
+    protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
         if (selected) {
             if (mOnItemViewSelectedListener != null) {
                 mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
@@ -279,7 +412,17 @@
                 mOnItemSelectedListener.onItemSelected(null, vh.getRow());
             }
         }
+    }
+
+    /**
+     * Subclass may override this to respond to selected state changes of a Row.
+     * Subclass may make visual changes to Row view but must not create
+     * animation on the Row view.
+     */
+    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
+        dispatchItemSelectedListener(vh, selected);
         updateHeaderViewVisibility(vh);
+        updateActivateStatus(vh, vh.view);
     }
 
     private void updateHeaderViewVisibility(ViewHolder vh) {
@@ -490,8 +633,22 @@
 
     /**
      * Freeze/Unfreeze the row, typically used when transition starts/ends.
+     * This method is called by fragment, app should not call it directly.
      */
     public void freeze(ViewHolder holder, boolean freeze) {
     }
 
+    /**
+     * Change visibility of views, entrance transition will be run against the views that
+     * change visibilities.  Subclass may override and begin with calling
+     * super.setEntranceTransitionState().  This method is called by fragment,
+     * app should not call it directly.
+     */
+    public void setEntranceTransitionState(ViewHolder holder, boolean afterTransition) {
+        if (holder.mHeaderViewHolder != null &&
+                holder.mHeaderViewHolder.view.getVisibility() != View.GONE) {
+            holder.mHeaderViewHolder.view.setVisibility(afterTransition ?
+                    View.VISIBLE : View.INVISIBLE);
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
new file mode 100644
index 0000000..362744e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Subclass of FrameLayout that support scale layout area size for children.
+ * @hide
+ */
+public class ScaleFrameLayout extends FrameLayout {
+
+    private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
+
+    private float mLayoutScaleX = 1f;
+    private float mLayoutScaleY = 1f;
+
+    public ScaleFrameLayout(Context context) {
+        this(context ,null);
+    }
+
+    public ScaleFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ScaleFrameLayout(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setLayoutScaleX(float scaleX) {
+        if (scaleX != mLayoutScaleX) {
+            mLayoutScaleX = scaleX;
+            requestLayout();
+        }
+    }
+
+    public void setLayoutScaleY(float scaleY) {
+        if (scaleY != mLayoutScaleY) {
+            mLayoutScaleY = scaleY;
+            requestLayout();
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int count = getChildCount();
+
+        final int parentLeft, parentRight;
+        final int layoutDirection = getLayoutDirection();
+        final float pivotX = (layoutDirection == View.LAYOUT_DIRECTION_RTL) ?
+                getWidth() - getPivotX() :
+                getPivotX();
+        if (mLayoutScaleX != 1f) {
+            parentLeft = getPaddingLeft() + (int)(pivotX - pivotX / mLayoutScaleX + 0.5f);
+            parentRight = (int)(pivotX + (right - left - pivotX) / mLayoutScaleX + 0.5f)
+                    - getPaddingRight();
+        } else {
+            parentLeft = getPaddingLeft();
+            parentRight = right - left - getPaddingRight();
+        }
+
+        final int parentTop, parentBottom;
+        final float pivotY = getPivotY();
+        if (mLayoutScaleY != 1f) {
+            parentTop = getPaddingTop() + (int)(pivotY - pivotY / mLayoutScaleY + 0.5f);
+            parentBottom = (int)(pivotY + (bottom - top - pivotY) / mLayoutScaleY + 0.5f)
+                    - getPaddingBottom();
+        } else {
+            parentTop = getPaddingTop();
+            parentBottom = bottom - top - getPaddingBottom();
+        }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                final int width = child.getMeasuredWidth();
+                final int height = child.getMeasuredHeight();
+
+                int childLeft;
+                int childTop;
+
+                int gravity = lp.gravity;
+                if (gravity == -1) {
+                    gravity = DEFAULT_CHILD_GRAVITY;
+                }
+
+                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+                                lp.leftMargin - lp.rightMargin;
+                        break;
+                    case Gravity.RIGHT:
+                        childLeft = parentRight - width - lp.rightMargin;
+                        break;
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = parentLeft + lp.leftMargin;
+                }
+
+                switch (verticalGravity) {
+                    case Gravity.TOP:
+                        childTop = parentTop + lp.topMargin;
+                        break;
+                    case Gravity.CENTER_VERTICAL:
+                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+                                lp.topMargin - lp.bottomMargin;
+                        break;
+                    case Gravity.BOTTOM:
+                        childTop = parentBottom - height - lp.bottomMargin;
+                        break;
+                    default:
+                        childTop = parentTop + lp.topMargin;
+                }
+
+                child.layout(childLeft, childTop, childLeft + width, childTop + height);
+                // synchronize child pivot to be same as ScaleFrameLayout's pivot
+                child.setPivotX(pivotX - childLeft);
+                child.setPivotY(pivotY - childTop);
+            }
+        }
+    }
+
+    private static int getScaledMeasureSpec(int measureSpec, float scale) {
+        return scale == 1f ? measureSpec : MeasureSpec.makeMeasureSpec(
+                (int) (MeasureSpec.getSize(measureSpec) / scale + 0.5f),
+                MeasureSpec.getMode(measureSpec));
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mLayoutScaleX != 1f || mLayoutScaleY != 1f) {
+            final int scaledWidthMeasureSpec =
+                    getScaledMeasureSpec(widthMeasureSpec, mLayoutScaleX);
+            final int scaledHeightMeasureSpec =
+                    getScaledMeasureSpec(heightMeasureSpec, mLayoutScaleY);
+            super.onMeasure(scaledWidthMeasureSpec, scaledHeightMeasureSpec);
+            setMeasuredDimension((int)(getMeasuredWidth() * mLayoutScaleX + 0.5f),
+                    (int)(getMeasuredHeight() * mLayoutScaleY + 0.5f));
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    /**
+     * setForeground() is not supported,  throws UnsupportedOperationException() when called.
+     */
+    @Override
+    public void setForeground(Drawable d) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
index 0d22284..c46ed5e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -107,6 +107,7 @@
     private SpeechOrbView mSpeechOrbView;
     private ImageView mBadgeView;
     private String mSearchQuery;
+    private String mHint;
     private String mTitle;
     private Drawable mBadgeDrawable;
     private final Handler mHandler = new Handler();
@@ -191,7 +192,7 @@
                 if (hasFocus) {
                     showNativeKeyboard();
                 }
-                updateUi();
+                updateUi(hasFocus);
             }
         });
         final Runnable mOnTextChangedRunnable = new Runnable() {
@@ -236,8 +237,9 @@
             public boolean onEditorAction(TextView textView, int action, KeyEvent keyEvent) {
                 if (DEBUG) Log.v(TAG, "onEditorAction: " + action + " event: " + keyEvent);
                 boolean handled = true;
-                if (EditorInfo.IME_ACTION_SEARCH == action && null != mSearchBarListener) {
-                    if (DEBUG) Log.v(TAG, "Action Pressed");
+                if ((EditorInfo.IME_ACTION_SEARCH == action ||
+                        EditorInfo.IME_NULL == action) && null != mSearchBarListener) {
+                    if (DEBUG) Log.v(TAG, "Action or enter pressed");
                     hideNativeKeyboard();
                     mHandler.postDelayed(new Runnable() {
                         @Override
@@ -298,11 +300,11 @@
                 } else {
                     stopRecognition();
                 }
-                updateUi();
+                updateUi(hasFocus);
             }
         });
 
-        updateUi();
+        updateUi(hasFocus());
         updateHint();
     }
 
@@ -371,7 +373,7 @@
      * Returns the current search bar hint text.
      */
     public CharSequence getHint() {
-        return (mSearchTextEditor == null) ? null : mSearchTextEditor.getHint();
+        return mHint;
     }
 
     /**
@@ -468,8 +470,6 @@
      * This will update the hint for the search bar properly depending on state and provided title
      */
     private void updateHint() {
-        if (null == mSearchTextEditor) return;
-
         String title = getResources().getString(R.string.lb_search_bar_hint);
         if (!TextUtils.isEmpty(mTitle)) {
             if (isVoiceMode()) {
@@ -480,7 +480,10 @@
         } else if (isVoiceMode()) {
             title = getResources().getString(R.string.lb_search_bar_hint_speech);
         }
-        mSearchTextEditor.setHint(title);
+        mHint = title;
+        if (mSearchTextEditor != null) {
+            mSearchTextEditor.setHint(mHint);
+        }
     }
 
     private void toggleRecognition() {
@@ -499,6 +502,12 @@
                 mListening, mRecognizing));
 
         if (!mRecognizing) return;
+
+        // Edit text content was cleared when starting recogition; ensure the content is restored
+        // in error cases
+        mSearchTextEditor.setText(mSearchQuery);
+        mSearchTextEditor.setHint(mHint);
+
         mRecognizing = false;
 
         if (mSpeechRecognitionCallback != null || null == mSpeechRecognizer) return;
@@ -528,6 +537,7 @@
         }
         if (mSpeechRecognitionCallback != null) {
             mSearchTextEditor.setText("");
+            mSearchTextEditor.setHint("");
             mSpeechRecognitionCallback.recognizeSpeech();
             return;
         }
@@ -673,14 +683,16 @@
         mSpeechRecognizer.startListening(recognizerIntent);
     }
 
-    private void updateUi() {
-        if (DEBUG) Log.v(TAG, String.format("Update UI %s %s",
-                isVoiceMode() ? "Voice" : "Text",
-                hasFocus() ? "Focused" : "Unfocused"));
-        if (isVoiceMode()) {
+    private void updateUi(boolean hasFocus) {
+        if (hasFocus) {
             mBarBackground.setAlpha(mBackgroundSpeechAlpha);
-            mSearchTextEditor.setTextColor(mTextColorSpeechMode);
-            mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+            if (isVoiceMode()) {
+                mSearchTextEditor.setTextColor(mTextHintColorSpeechMode);
+                mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+            } else {
+                mSearchTextEditor.setTextColor(mTextColorSpeechMode);
+                mSearchTextEditor.setHintTextColor(mTextHintColorSpeechMode);
+            }
         } else {
             mBarBackground.setAlpha(mBackgroundAlpha);
             mSearchTextEditor.setTextColor(mTextColor);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
new file mode 100644
index 0000000..1167746
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
@@ -0,0 +1,126 @@
+package android.support.v17.leanback.widget;
+
+import android.util.SparseArray;
+
+/**
+ * An ObjectAdapter implemented with a {@link android.util.SparseArray}.
+ * This class maintains an array of objects where each object is associated
+ * with an integer key which determines its order relative to other objects.
+ */
+public class SparseArrayObjectAdapter extends ObjectAdapter {
+    private SparseArray<Object> mItems = new SparseArray<Object>();
+
+    /**
+     * Construct an adapter with the given {@link PresenterSelector}.
+     */
+    public SparseArrayObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    /**
+     * Construct an adapter with the given {@link Presenter}.
+     */
+    public SparseArrayObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    /**
+     * Construct an adapter.
+     */
+    public SparseArrayObjectAdapter() {
+        super();
+    }
+
+    @Override
+    public int size() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object get(int position) {
+        return mItems.valueAt(position);
+    }
+
+    /**
+     * Returns the index for the given item in the adapter.
+     *
+     * @param item  The item to find in the array.
+     * @return Index of the item, or a negative value if not found.
+     */
+    public int indexOf(Object item) {
+        return mItems.indexOfValue(item);
+    }
+
+    /**
+     * Returns the index for the given key in the adapter.
+     *
+     * @param key The key to find in the array.
+     * @return Index of the item, or a negative value if not found.
+     */
+    public int indexOf(int key) {
+        return mItems.indexOfKey(key);
+    }
+
+    /**
+     * Notify that the content of a range of items changed. Note that this is
+     * not same as items being added or removed.
+     *
+     * @param positionStart The position of first item that has changed.
+     * @param itemCount The count of how many items have changed.
+     */
+    public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
+        notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    /**
+     * Sets the item for the given key.
+     *
+     * @param key The key associated with the item.
+     * @param item The item associated with the key.
+     */
+    public void set(int key, Object item) {
+        int index = mItems.indexOfKey(key);
+        if (index >= 0) {
+            if (mItems.valueAt(index) != item) {
+                mItems.setValueAt(index, item);
+                notifyItemRangeChanged(index, 1);
+            }
+        } else {
+            mItems.append(key, item);
+            index = mItems.indexOfKey(key);
+            notifyItemRangeInserted(index, 1);
+        }
+    }
+
+    /**
+     * Clears the given key and associated item from the adapter.
+     *
+     * @param key The key to be cleared.
+     */
+    public void clear(int key) {
+        int index = mItems.indexOfKey(key);
+        if (index >= 0) {
+            mItems.removeAt(index);
+            notifyItemRangeRemoved(index, 1);
+        }
+    }
+
+    /**
+     * Removes all items from this adapter, leaving it empty.
+     */
+    public void clear() {
+        final int itemCount = mItems.size();
+        if (itemCount == 0) {
+            return;
+        }
+        mItems.clear();
+        notifyItemRangeRemoved(0, itemCount);
+    }
+
+    /**
+     * Returns the object for the given key, or null if no mapping for that key exists.
+     */
+    public Object lookup(int key) {
+        return mItems.get(key);
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
index 5404418..53137e6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -14,13 +14,14 @@
 package android.support.v17.leanback.widget;
 
 import android.support.v4.util.CircularArray;
+import android.support.v4.util.CircularIntArray;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A dynamic data structure that maintains staggered grid position information
+ * A dynamic data structure that caches staggered grid position information
  * for each individual child. The algorithm ensures that each row will be kept
  * as balanced as possible when prepending and appending a child.
  *
@@ -32,141 +33,60 @@
  * scrolls back to 0 and we don't keep history location information, edges of
  * the very beginning of rows will not be aligned. It is recommended to keep a
  * list of tens of thousands of {@link StaggeredGrid.Location}s which will be
- * big enough to remember a typical user's scroll history. There are situations
- * where StaggeredGrid falls back to the simple case where we do not need save a
- * huge list of locations inside StaggeredGrid:
- * <ul>
- *   <li>Only one row (e.g., a single row listview)</li>
- *   <li> Each item has the same length (not staggered at all)</li>
- * </ul>
+ * big enough to remember a typical user's scroll history.
  *
  * <p>
  * This class is abstract and can be replaced with different implementations.
  */
-abstract class StaggeredGrid {
+abstract class StaggeredGrid extends Grid {
+
+    private static final int OFFSET_UNDEFINED = Integer.MAX_VALUE;
 
     /**
-     * TODO: document this
+     * Cached representation of Staggered item.
      */
-    public static interface Provider {
+    public static class Location extends Grid.Location {
         /**
-         * Return how many items are in the adapter.
+         * Offset to previous item location.
+         * min_edge(index) - min_edge(index - 1) for non reversed case
+         * max_edge(index) - max_edge(index - 1) for reversed case
          */
-        public abstract int getCount();
+        public int offset;
 
         /**
-         * Create the object at a given row.
+         * size of the item.
          */
-        public abstract void createItem(int index, int row, boolean append);
-    }
+        public int size;
 
-    /**
-     * Location of an item in the grid. For now it only saves row index but
-     * more information may be added in the future.
-     */
-    public final static class Location {
-        /**
-         * The index of the row for this Location.
-         */
-        public final int row;
-
-        /**
-         * Create a Location with the given row index.
-         */
-        public Location(int row) {
-            this.row = row;
+        public Location(int row, int offset, int size) {
+            super(row);
+            this.offset = offset;
+            this.size = size;
         }
     }
 
-    /**
-     * TODO: document this
-     */
-    public final static class Row {
-        /**
-         * first view start location
-         */
-        public int low;
-        /**
-         * last view end location
-         */
-        public int high;
-    }
-
-    protected Provider mProvider;
-    protected int mNumRows = 1; // mRows.length
-    protected Row[] mRows;
     protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
-    private ArrayList<Integer>[] mTmpItemPositionsInRows;
 
-    /**
-     * A constant representing a default starting index, indicating that the
-     * developer did not provide a start index.
-     */
-    public static final int START_DEFAULT = -1;
-
-    // the first index that grid will layout
-    protected int mStartIndex = START_DEFAULT;
-    // the row to layout the first index
-    protected int mStartRow = START_DEFAULT;
-
+    // mFirstIndex <= mFirstVisibleIndex <= mLastVisibleIndex
+    //    <= mFirstIndex + mLocations.size() - 1
     protected int mFirstIndex = -1;
 
-    /**
-     * Sets the {@link Provider} for this staggered grid.
-     *
-     * @param provider The provider for this staggered grid.
-     */
-    public void setProvider(Provider provider) {
-        mProvider = provider;
-    }
+    private Object[] mTmpItem = new Object[1];
+
+    protected Object mPendingItem;
+    protected int mPendingItemSize;
 
     /**
-     * Sets the array of {@link Row}s to fill into. For views that represent a
-     * horizontal list, this will be the rows of the view. For views that
-     * represent a vertical list, this will be the columns.
-     *
-     * @param row The array of {@link Row}s to be filled.
-     */
-    public final void setRows(Row[] row) {
-        if (row == null || row.length == 0) {
-            throw new IllegalArgumentException();
-        }
-        mNumRows = row.length;
-        mRows = row;
-        mTmpItemPositionsInRows = new ArrayList[mNumRows];
-        for (int i = 0; i < mNumRows; i++) {
-            mTmpItemPositionsInRows[i] = new ArrayList(32);
-        }
-    }
-
-    /**
-     * Returns the number of rows in the staggered grid.
-     */
-    public final int getNumRows() {
-        return mNumRows;
-    }
-
-    /**
-     * Set the first item index and the row index to load when there are no
-     * items.
-     *
-     * @param startIndex the index of the first item
-     * @param startRow the index of the row
-     */
-    public final void setStart(int startIndex, int startRow) {
-        mStartIndex = startIndex;
-        mStartRow = startRow;
-    }
-
-    /**
-     * Returns the first index in the staggered grid.
+     * Returns index of first item (cached or visible) in the staggered grid.
+     * Returns negative value if no item.
      */
     public final int getFirstIndex() {
         return mFirstIndex;
     }
 
     /**
-     * Returns the last index in the staggered grid.
+     * Returns index of last item (cached or visible) in the staggered grid.
+     * Returns negative value if no item.
      */
     public final int getLastIndex() {
         return mFirstIndex + mLocations.size() - 1;
@@ -179,9 +99,7 @@
         return mLocations.size();
     }
 
-    /**
-     * Returns the {@link Location} at the given index.
-     */
+    @Override
     public final Location getLocation(int index) {
         if (mLocations.size() == 0) {
             return null;
@@ -189,21 +107,7 @@
         return mLocations.get(index - mFirstIndex);
     }
 
-    /**
-     * Removes the first element.
-     */
-    public final void removeFirst() {
-        mFirstIndex++;
-        mLocations.popFirst();
-    }
-
-    /**
-     * Removes the last element.
-     */
-    public final void removeLast() {
-        mLocations.popLast();
-    }
-
+    @Override
     public final void debugPrint(PrintWriter pw) {
         for (int i = 0, size = mLocations.size(); i < size; i++) {
             Location loc = mLocations.get(i);
@@ -213,97 +117,296 @@
         }
     }
 
-    protected final int getMaxHighRowIndex() {
-        int maxHighRowIndex = 0;
-        for (int i = 1; i < mNumRows; i++) {
-            if (mRows[i].high > mRows[maxHighRowIndex].high) {
-                maxHighRowIndex = i;
-            }
+    @Override
+    protected final boolean prependVisibleItems(int toLimit, boolean oneColumnMode) {
+        if (mProvider.getCount() == 0) {
+            return false;
         }
-        return maxHighRowIndex;
+        if (!oneColumnMode && checkPrependOverLimit(toLimit)) {
+            return false;
+        }
+        try {
+            if (prependVisbleItemsWithCache(toLimit, oneColumnMode)) {
+                return true;
+            }
+            return prependVisibleItemsWithoutCache(toLimit, oneColumnMode);
+        } finally {
+            mTmpItem[0] = null;
+            mPendingItem = null;
+        }
     }
 
-    protected final int getMinHighRowIndex() {
-        int minHighRowIndex = 0;
-        for (int i = 1; i < mNumRows; i++) {
-            if (mRows[i].high < mRows[minHighRowIndex].high) {
-                minHighRowIndex = i;
-            }
-        }
-        return minHighRowIndex;
-    }
-
-    protected final Location appendItemToRow(int itemIndex, int rowIndex) {
-        Location loc = new Location(rowIndex);
+    /**
+     * Prepends items using cached locations,  returning true if toLimit is reached.
+     * This method should only be called by prependVisibleItems().
+     */
+    protected final boolean prependVisbleItemsWithCache(int toLimit, boolean oneColumnMode) {
         if (mLocations.size() == 0) {
-            mFirstIndex = itemIndex;
+            return false;
+        }
+        final int count = mProvider.getCount();
+        final int firstIndex = getFirstIndex();
+        int itemIndex;
+        int edge;
+        int offset;
+        if (mFirstVisibleIndex >= 0) {
+            // prepend visible items from first visible index
+            edge = mProvider.getEdge(mFirstVisibleIndex);
+            // Note offset of first visible item can be OFFSET_UNDEFINED.
+            offset = getLocation(mFirstVisibleIndex).offset;
+            itemIndex = mFirstVisibleIndex - 1;
+        } else {
+            // prepend first visible item
+            edge = Integer.MAX_VALUE;
+            offset = 0;
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+        }
+        for (; itemIndex >= mFirstIndex; itemIndex--) {
+            Location loc = getLocation(itemIndex);
+            int rowIndex = loc.row;
+            int size = mProvider.createItem(itemIndex, false, mTmpItem);
+            if (size != loc.size) {
+                mLocations.removeFromStart(itemIndex + 1 - mFirstIndex);
+                mFirstIndex = mFirstVisibleIndex;
+                // pending item will be added in prependVisibleItemsWithoutCache
+                mPendingItem = mTmpItem[0];
+                mPendingItemSize = size;
+                return false;
+            }
+            if (offset == OFFSET_UNDEFINED) {
+                offset = updateFirstVisibleOffset(size);
+            }
+            mFirstVisibleIndex = itemIndex;
+            if (mLastVisibleIndex < 0) {
+                mLastVisibleIndex = itemIndex;
+            }
+            mProvider.addItem(mTmpItem[0], itemIndex, size, rowIndex, edge - offset);
+            if (!oneColumnMode && checkPrependOverLimit(toLimit)) {
+                return true;
+            }
+            edge = mProvider.getEdge(itemIndex);
+            offset = loc.offset;
+            // Check limit after filled a full column
+            if (rowIndex == 0) {
+                if (oneColumnMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * When we append first visible item without cache after cached items, the offset
+     * of the first visible item cannot be determined until we prepend previous item with cache.
+     * This method is called from prependVisbleItemsWithCache() to update the offset
+     * of first visible item.
+     * @param prependedItemSize   Size of the prepended item.
+     * @return                    Updated size of current first visible item.
+     */
+    protected abstract int updateFirstVisibleOffset(int prependedItemSize);
+
+    /**
+     * This implements the algorithm of layout staggered grid, the method should only be called by
+     * prependVisibleItems().
+     */
+    protected abstract boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode);
+
+    /**
+     * Prepends one visible item with new Location info.  Only called from
+     * prependVisibleItemsWithoutCache().
+     */
+    protected final int prependVisibleItemToRow(int itemIndex, int rowIndex, int edge) {
+        int offset;
+        if (mFirstVisibleIndex >= 0) {
+            if (mFirstVisibleIndex != getFirstIndex() || mFirstVisibleIndex != itemIndex + 1) {
+                // should never hit this when we prepend a new item with a new Location object.
+                throw new IllegalStateException();
+            }
+        }
+        Location oldFirstLoc = mFirstIndex >= 0 ? getLocation(mFirstIndex) : null;
+        int oldFirstEdge = mProvider.getEdge(mFirstIndex);
+        Location loc = new Location(rowIndex, 0, 0);
+        Object item;
+        if (mPendingItem != null) {
+            loc.size = mPendingItemSize;
+            item = mPendingItem;
+            mPendingItem = null;
+        } else {
+            loc.size = mProvider.createItem(itemIndex, false, mTmpItem);
+            item = mTmpItem[0];
+        }
+        mFirstIndex = mFirstVisibleIndex = itemIndex;
+        if (mLastVisibleIndex < 0) {
+            mLastVisibleIndex = itemIndex;
+        }
+        mLocations.addFirst(loc);
+        int thisEdge = !mReversedFlow ? edge - loc.size : edge + loc.size;
+        if (oldFirstLoc != null) {
+            oldFirstLoc.offset = oldFirstEdge - thisEdge;
+        }
+        mProvider.addItem(item, itemIndex, loc.size, rowIndex, thisEdge);
+        return loc.size;
+    }
+
+    @Override
+    protected final boolean appendVisibleItems(int toLimit, boolean oneColumnMode) {
+        if (mProvider.getCount() == 0) {
+            return false;
+        }
+        if (!oneColumnMode && checkAppendOverLimit(toLimit)) {
+            return false;
+        }
+        try {
+            if (appendVisbleItemsWithCache(toLimit, oneColumnMode)) {
+                return true;
+            }
+            return appendVisibleItemsWithoutCache(toLimit, oneColumnMode);
+        } finally {
+            mTmpItem[0] = null;
+            mPendingItem = null;
+        }
+    }
+
+    /**
+     * Appends items using cached locations,  returning true if at least one item is appended
+     * and (oneColumnMode is true or reach limit and aboveIndex).
+     * This method should only be called by appendVisibleItems()
+     */
+    protected final boolean appendVisbleItemsWithCache(int toLimit, boolean oneColumnMode) {
+        if (mLocations.size() == 0) {
+            return false;
+        }
+        final int count = mProvider.getCount();
+        int itemIndex;
+        int edge;
+        if (mLastVisibleIndex >= 0) {
+            // append visible items from last visible index
+            itemIndex = mLastVisibleIndex + 1;
+            edge = mProvider.getEdge(mLastVisibleIndex);
+        } else {
+            // append first visible item
+            edge = Integer.MAX_VALUE;
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            for (int i = itemIndex - 1; i >= mFirstIndex; i--) {
+                if (getLocation(i).row == mNumRows - 1) {
+                    itemIndex = i + 1;
+                    break;
+                }
+            }
+        }
+        int lastIndex = getLastIndex();
+        for (; itemIndex < count && itemIndex <= lastIndex; itemIndex++) {
+            Location loc = getLocation(itemIndex);
+            if (edge != Integer.MAX_VALUE) {
+                edge = edge + loc.offset;
+            }
+            int rowIndex = loc.row;
+            int size = mProvider.createItem(itemIndex, true, mTmpItem);
+            if (size != loc.size) {
+                loc.size = size;
+                mLocations.removeFromEnd(lastIndex - itemIndex);
+                lastIndex = itemIndex;
+            }
+            mLastVisibleIndex = itemIndex;
+            if (mFirstVisibleIndex < 0) {
+                mFirstVisibleIndex = itemIndex;
+            }
+            mProvider.addItem(mTmpItem[0], itemIndex, size, rowIndex, edge);
+            if (!oneColumnMode && checkAppendOverLimit(toLimit)) {
+                return true;
+            }
+            if (edge == Integer.MAX_VALUE) {
+                edge = mProvider.getEdge(itemIndex);
+            }
+            // Check limit after filled a full column
+            if (rowIndex == mNumRows - 1) {
+                if (oneColumnMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * algorithm of layout staggered grid, this method should only be called by
+     * appendVisibleItems().
+     */
+    protected abstract boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode);
+
+    /**
+     * Appends one visible item with new Location info.  Only called from
+     * appendVisibleItemsWithoutCache().
+     */
+    protected final int appendVisibleItemToRow(int itemIndex, int rowIndex, int location) {
+        int offset;
+        if (mLastVisibleIndex >= 0) {
+            if (mLastVisibleIndex != getLastIndex() || mLastVisibleIndex != itemIndex - 1) {
+                // should never hit this when we append a new item with a new Location object.
+                throw new IllegalStateException();
+            }
+        }
+        if (mLastVisibleIndex < 0) {
+            // if we append first visible item after existing cached items,  we need update
+            // the offset later when prependVisbleItemsWithCache()
+            offset = OFFSET_UNDEFINED;
+        } else {
+            offset = location - mProvider.getEdge(mLastVisibleIndex);
+        }
+        Location loc = new Location(rowIndex, offset, 0);
+        Object item;
+        if (mPendingItem != null) {
+            loc.size = mPendingItemSize;
+            item = mPendingItem;
+            mPendingItem = null;
+        } else {
+            loc.size = mProvider.createItem(itemIndex, true, mTmpItem);
+            item = mTmpItem[0];
+        }
+        if (mLocations.size() == 0) {
+            mFirstIndex = mFirstVisibleIndex = mLastVisibleIndex = itemIndex;
+        } else {
+            if (mLastVisibleIndex < 0) {
+                mFirstVisibleIndex = mLastVisibleIndex = itemIndex;
+            } else {
+                mLastVisibleIndex++;
+            }
         }
         mLocations.addLast(loc);
-        mProvider.createItem(itemIndex, rowIndex, true);
-        return loc;
+        mProvider.addItem(item, itemIndex, loc.size, rowIndex, location);
+        return loc.size;
     }
 
-    /**
-     * Append items until the high edge reaches upTo.
-     */
-    public abstract void appendItems(int upTo);
-
-    protected final int getMaxLowRowIndex() {
-        int maxLowRowIndex = 0;
-        for (int i = 1; i < mNumRows; i++) {
-            if (mRows[i].low > mRows[maxLowRowIndex].low) {
-                maxLowRowIndex = i;
-            }
-        }
-        return maxLowRowIndex;
-    }
-
-    protected final int getMinLowRowIndex() {
-        int minLowRowIndex = 0;
-        for (int i = 1; i < mNumRows; i++) {
-            if (mRows[i].low < mRows[minLowRowIndex].low) {
-                minLowRowIndex = i;
-            }
-        }
-        return minLowRowIndex;
-    }
-
-    protected final Location prependItemToRow(int itemIndex, int rowIndex) {
-        Location loc = new Location(rowIndex);
-        mFirstIndex = itemIndex;
-        mLocations.addFirst(loc);
-        mProvider.createItem(itemIndex, rowIndex, false);
-        return loc;
-    }
-
-    /**
-     * Return array of Lists for all rows, each List contains item positions
-     * on that row between startPos(included) and endPositions(included).
-     * Returned value is read only, do not change it.
-     */
-    public final List<Integer>[] getItemPositionsInRows(int startPos, int endPos) {
+    @Override
+    public final CircularIntArray[] getItemPositionsInRows(int startPos, int endPos) {
         for (int i = 0; i < mNumRows; i++) {
             mTmpItemPositionsInRows[i].clear();
         }
         if (startPos >= 0) {
             for (int i = startPos; i <= endPos; i++) {
-                mTmpItemPositionsInRows[getLocation(i).row].add(i);
+                CircularIntArray row = mTmpItemPositionsInRows[getLocation(i).row];
+                if (row.size() > 0 && row.getLast() == i - 1) {
+                    // update continuous range
+                    row.popLast();
+                    row.addLast(i);
+                } else {
+                    // add single position
+                    row.addLast(i);
+                    row.addLast(i);
+                }
             }
         }
         return mTmpItemPositionsInRows;
     }
 
-    /**
-     * Prepend items until the low edge reaches downTo.
-     */
-    public abstract void prependItems(int downTo);
+    @Override
+    public void invalidateItemsAfter(int index) {
+        super.invalidateItemsAfter(index);
+        mLocations.removeFromEnd(getLastIndex() - index + 1);
+        if (mLocations.size() == 0) {
+            mFirstIndex = -1;
+        }
+    }
 
-    /**
-     * Strip items, keep a contiguous subset of items; the subset should include
-     * at least one item on every row that currently has at least one item.
-     *
-     * <p>
-     * TODO: document this better
-     */
-    public abstract void stripDownTo(int itemIndex);
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
index 9f2a06c..4675743 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
@@ -22,131 +22,434 @@
 final class StaggeredGridDefault extends StaggeredGrid {
 
     @Override
-    public void appendItems(int upTo) {
-        int count = mProvider.getCount();
+    protected int updateFirstVisibleOffset(int prependedItemSize) {
+        Location firstVisible = getLocation(mFirstVisibleIndex);
+        // Find a cached item in same row, if not found, just use last item.
+        int index = mFirstVisibleIndex - 1;
+        boolean foundCachedItemInSameRow = false;
+        while (index >= mFirstIndex) {
+            Location loc = getLocation(index);
+            if (loc.row == firstVisible.row) {
+                foundCachedItemInSameRow = true;
+                break;
+            }
+            index--;
+        }
+        if (!foundCachedItemInSameRow) {
+            index = mFirstVisibleIndex - 1;
+        }
+        // Assuming mFirstVisibleIndex is next to index on the same row, so the
+        // sum of offset of [index + 1, mFirstVisibleIndex] should be size of the item
+        // plus margin.
+        int offset = isReversedFlow() ?  -getLocation(index).size - mMargin:
+                getLocation(index).size + mMargin;
+        for (int i = index + 1; i < mFirstVisibleIndex; i++) {
+            offset -= getLocation(index).offset;
+        }
+        firstVisible.offset = offset;
+        return offset;
+    }
+
+    /**
+     * Returns the max edge value of item (visible or cached) in a row.  This
+     * will be the place to append or prepend item not in cache.
+     */
+    int getRowMax(int rowIndex) {
+        if (mFirstVisibleIndex < 0) {
+            return Integer.MIN_VALUE;
+        }
+        if (mReversedFlow) {
+            int edge = mProvider.getEdge(mFirstVisibleIndex);
+            if (getLocation(mFirstVisibleIndex).row == rowIndex) {
+                return edge;
+            }
+            for (int i = mFirstVisibleIndex + 1; i <= getLastIndex(); i++) {
+                Location loc = getLocation(i);
+                edge += loc.offset;
+                if (loc.row == rowIndex) {
+                    return edge;
+                }
+            }
+        } else {
+            int edge = mProvider.getEdge(mLastVisibleIndex);
+            Location loc = getLocation(mLastVisibleIndex);
+            if (loc.row == rowIndex) {
+                return edge + loc.size;
+            }
+            for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) {
+                edge -= loc.offset;
+                loc = getLocation(i);
+                if (loc.row == rowIndex) {
+                    return edge + loc.size;
+                }
+            }
+        }
+        return Integer.MIN_VALUE;
+    }
+
+    /**
+     * Returns the min edge value of item (visible or cached) in a row.  This
+     * will be the place to prepend or append item not in cache.
+     */
+    int getRowMin(int rowIndex) {
+        if (mFirstVisibleIndex < 0) {
+            return Integer.MAX_VALUE;
+        }
+        if (mReversedFlow) {
+            int edge = mProvider.getEdge(mLastVisibleIndex);
+            Location loc = getLocation(mLastVisibleIndex);
+            if (loc.row == rowIndex) {
+                return edge - loc.size;
+            }
+            for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) {
+                edge -= loc.offset;
+                loc = getLocation(i);
+                if (loc.row == rowIndex) {
+                    return edge - loc.size;
+                }
+            }
+        } else {
+            int edge = mProvider.getEdge(mFirstVisibleIndex);
+            if (getLocation(mFirstVisibleIndex).row == rowIndex) {
+                return edge;
+            }
+            for (int i = mFirstVisibleIndex + 1; i <= getLastIndex() ; i++) {
+                Location loc = getLocation(i);
+                edge += loc.offset;
+                if (loc.row == rowIndex) {
+                    return edge;
+                }
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Note this method has assumption that item is filled either in the same row
+     * next row of last item.  Search until row index wrapped.
+     */
+    @Override
+    public int findRowMax(boolean findLarge, int indexLimit, int[] indices) {
+        int value;
+        int edge = mProvider.getEdge(indexLimit);
+        Location loc = getLocation(indexLimit);
+        int row = loc.row;
+        int index = indexLimit;
+        int visitedRows = 1;
+        int visitRow = row;
+        if (mReversedFlow) {
+            value = edge;
+            for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) {
+                loc = getLocation(i);
+                edge += loc.offset;
+                if (loc.row != visitRow) {
+                    visitRow = loc.row;
+                    visitedRows++;
+                    if (findLarge ? edge > value : edge < value) {
+                        row = visitRow;
+                        value = edge;
+                        index = i;
+                    }
+                }
+            }
+        } else {
+            value = edge + mProvider.getSize(indexLimit);
+            for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) {
+                edge -= loc.offset;
+                loc = getLocation(i);
+                if (loc.row != visitRow) {
+                    visitRow = loc.row;
+                    visitedRows++;
+                    int newValue = edge + mProvider.getSize(i);
+                    if (findLarge ? newValue > value : newValue < value) {
+                        row = visitRow;
+                        value = newValue;
+                        index = i;
+                    }
+                }
+            }
+        }
+        if (indices != null) {
+            indices[0] = row;
+            indices[1] = index;
+        }
+        return value;
+    }
+
+    /**
+     * Note this method has assumption that item is filled either in the same row
+     * next row of last item.  Search until row index wrapped.
+     */
+    @Override
+    public int findRowMin(boolean findLarge, int indexLimit, int[] indices) {
+        int value;
+        int edge = mProvider.getEdge(indexLimit);
+        Location loc = getLocation(indexLimit);
+        int row = loc.row;
+        int index = indexLimit;
+        int visitedRows = 1;
+        int visitRow = row;
+        if (mReversedFlow) {
+            value = edge - mProvider.getSize(indexLimit);
+            for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) {
+                edge -= loc.offset;
+                loc = getLocation(i);
+                if (loc.row != visitRow) {
+                    visitRow = loc.row;
+                    visitedRows++;
+                    int newValue = edge - mProvider.getSize(i);
+                    if (findLarge ? newValue > value : newValue < value) {
+                        value = newValue;
+                        row = visitRow;
+                        index = i;
+                    }
+                }
+            }
+        } else {
+            value = edge;
+            for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) {
+                loc = getLocation(i);
+                edge += loc.offset;
+                if (loc.row != visitRow) {
+                    visitRow = loc.row;
+                    visitedRows++;
+                    if (findLarge ? edge > value : edge < value) {
+                        value = edge;
+                        row = visitRow;
+                        index = i;
+                    }
+                }
+            }
+        }
+        if (indices != null) {
+            indices[0] = row;
+            indices[1] = index;
+        }
+        return value;
+    }
+
+    private int findRowEdgeLimitSearchIndex(boolean append) {
+        boolean wrapped = false;
+        if (append) {
+            for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) {
+                int row = getLocation(index).row;
+                if (row == 0) {
+                    wrapped = true;
+                } else if (wrapped && row == mNumRows - 1) {
+                    return index;
+                }
+            }
+        } else {
+            for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) {
+                int row = getLocation(index).row;
+                if (row == mNumRows - 1) {
+                    wrapped = true;
+                } else if (wrapped && row == 0) {
+                    return index;
+                }
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
+        final int count = mProvider.getCount();
         int itemIndex;
         int rowIndex;
-        if (mLocations.size() > 0) {
-            itemIndex = getLastIndex() + 1;
-            rowIndex = (mLocations.getLast().row + 1) % mNumRows;
+        int edgeLimit;
+        boolean edgeLimitIsValid;
+        if (mLastVisibleIndex >= 0) {
+            if (mLastVisibleIndex < getLastIndex()) {
+                // should fill using cache instead
+                return false;
+            }
+            itemIndex = mLastVisibleIndex + 1;
+            rowIndex = getLocation(mLastVisibleIndex).row;
+            // find start item index of "previous column"
+            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true);
+            if (edgeLimitSearchIndex < 0) {
+                // if "previous colummn" is not found, using edgeLimit of
+                // first row currently in grid
+                edgeLimit = Integer.MIN_VALUE;
+                for (int i = 0; i < mNumRows; i++) {
+                    edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i);
+                    if (edgeLimit != Integer.MIN_VALUE) {
+                        break;
+                    }
+                }
+            } else {
+                edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) :
+                        findRowMax(true, edgeLimitSearchIndex, null);
+            }
+            if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit
+                    : getRowMax(rowIndex) >= edgeLimit) {
+                // if current row exceeds previous column, fill from next row
+                rowIndex = rowIndex + 1;
+                if (rowIndex == mNumRows) {
+                    // start a new column and using edge limit of current column
+                    rowIndex = 0;
+                    edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
+                }
+            }
+            edgeLimitIsValid = true;
         } else {
             itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
-            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
+            rowIndex = itemIndex % mNumRows;
+            edgeLimit = 0;
+            edgeLimitIsValid = false;
         }
 
-    top_loop:
+        boolean filledOne = false;
         while (true) {
-            // find highest row (.high is biggest)
-            int maxHighRowIndex = mLocations.size() > 0 ? getMaxHighRowIndex() : -1;
-            int maxHigh = maxHighRowIndex != -1 ? mRows[maxHighRowIndex].high : Integer.MIN_VALUE;
+            // find end-most row edge (.high is biggest, or .low is smallest in reversed flow)
             // fill from current row till last row so that each row will grow longer than
             // the previous highest row.
             for (; rowIndex < mNumRows; rowIndex++) {
                 // fill one item to a row
-                if (itemIndex == count) {
-                    break top_loop;
+                if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) {
+                    return filledOne;
                 }
-                appendItemToRow(itemIndex++, rowIndex);
+                int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
+                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
+                    // nothing on the row
+                    if (rowIndex == 0) {
+                        location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1);
+                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
+                            location = location + (mReversedFlow ? -mMargin : mMargin);
+                        }
+                    } else {
+                        location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1);
+                    }
+                } else {
+                    location = location + (mReversedFlow ? -mMargin : mMargin);
+                }
+                int size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
+                filledOne = true;
                 // fill more item to the row to make sure this row is longer than
                 // the previous highest row.
-                if (maxHighRowIndex == -1) {
-                    maxHighRowIndex = getMaxHighRowIndex();
-                    maxHigh = mRows[maxHighRowIndex].high;
-                } else  if (rowIndex != maxHighRowIndex) {
-                    while (mRows[rowIndex].high < maxHigh) {
-                        if (itemIndex == count) {
-                            break top_loop;
+                if (edgeLimitIsValid) {
+                    while (mReversedFlow ? location - size > edgeLimit :
+                            location + size < edgeLimit) {
+                        if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) {
+                            return filledOne;
                         }
-                        appendItemToRow(itemIndex++, rowIndex);
+                        location = location + (mReversedFlow ? - size - mMargin : size + mMargin);
+                        size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
                     }
+                } else {
+                    edgeLimitIsValid = true;
+                    edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
                 }
             }
-            if (mRows[getMinHighRowIndex()].high >= upTo) {
-                break;
+            if (oneColumnMode) {
+                return filledOne;
             }
+            edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
             // start fill from row 0 again
             rowIndex = 0;
         }
     }
 
     @Override
-    public void prependItems(int downTo) {
-        if (mProvider.getCount() <= 0) return;
+    protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
         int itemIndex;
         int rowIndex;
-        if (mLocations.size() > 0) {
-            itemIndex = getFirstIndex() - 1;
-            rowIndex = mLocations.getFirst().row;
-            if (rowIndex == 0) {
-                rowIndex = mNumRows - 1;
-            } else {
-                rowIndex--;
+        int edgeLimit;
+        boolean edgeLimitIsValid;
+        if (mFirstVisibleIndex >= 0) {
+            if (mFirstVisibleIndex > getFirstIndex()) {
+                // should fill using cache instead
+                return false;
             }
-        } else {
-            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
-            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
-        }
-
-    top_loop:
-        while (true) {
-            int minLowRowIndex = mLocations.size() > 0 ? getMinLowRowIndex() : -1;
-            int minLow = minLowRowIndex != -1 ? mRows[minLowRowIndex].low : Integer.MAX_VALUE;
-            for (; rowIndex >=0 ; rowIndex--) {
-                if (itemIndex < 0) {
-                    break top_loop;
-                }
-                prependItemToRow(itemIndex--, rowIndex);
-                if (minLowRowIndex == -1) {
-                    minLowRowIndex = getMinLowRowIndex();
-                    minLow = mRows[minLowRowIndex].low;
-                } else if (rowIndex != minLowRowIndex) {
-                    while (mRows[rowIndex].low > minLow) {
-                        if (itemIndex < 0) {
-                            break top_loop;
-                        }
-                        prependItemToRow(itemIndex--, rowIndex);
+            itemIndex = mFirstVisibleIndex - 1;
+            rowIndex = getLocation(mFirstVisibleIndex).row;
+            // find start item index of "previous column"
+            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false);
+            if (edgeLimitSearchIndex < 0) {
+                // if "previous colummn" is not found, using edgeLimit of
+                // last row currently in grid and fill from upper row
+                rowIndex = rowIndex - 1;
+                edgeLimit = Integer.MAX_VALUE;
+                for (int i = mNumRows - 1; i >= 0; i--) {
+                    edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i);
+                    if (edgeLimit != Integer.MAX_VALUE) {
+                        break;
                     }
                 }
+            } else {
+                edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) :
+                        findRowMin(false, edgeLimitSearchIndex, null);
             }
-            if (mRows[getMaxLowRowIndex()].low <= downTo) {
-                break;
+            if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit
+                    : getRowMin(rowIndex) <= edgeLimit) {
+                // if current row exceeds previous column, fill from next row
+                rowIndex = rowIndex - 1;
+                if (rowIndex < 0) {
+                    // start a new column and using edge limit of current column
+                    rowIndex = mNumRows - 1;
+                    edgeLimit = mReversedFlow ? findRowMax(true, null) :
+                            findRowMin(false, null);
+                }
             }
+            edgeLimitIsValid = true;
+        } else {
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            rowIndex = itemIndex % mNumRows;
+            edgeLimit = 0;
+            edgeLimitIsValid = false;
+        }
+        boolean filledOne = false;
+        while (true) {
+            // find start-most row edge (.low is smallest, or .high is largest in reversed flow)
+            // fill from current row till first row so that each row will grow longer than
+            // the previous lowest row.
+            for (; rowIndex >= 0; rowIndex--) {
+                // fill one item to a row
+                if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) {
+                    return filledOne;
+                }
+                int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
+                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
+                    // nothing on the row
+                    if (rowIndex == mNumRows - 1) {
+                        location = mReversedFlow ? getRowMax(0) : getRowMin(0);
+                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
+                            location = location + (mReversedFlow ? mMargin : -mMargin);
+                        }
+                    } else {
+                        location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1);
+                    }
+                } else {
+                    location = location + (mReversedFlow ? mMargin : -mMargin);
+                }
+                int size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
+                filledOne = true;
+
+                // fill more item to the row to make sure this row is longer than
+                // the previous highest row.
+                if (edgeLimitIsValid) {
+                    while (mReversedFlow ? location + size < edgeLimit :
+                            location - size > edgeLimit) {
+                        if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) {
+                            return filledOne;
+                        }
+                        location = location + (mReversedFlow ? size + mMargin : -size - mMargin);
+                        size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
+                    }
+                } else {
+                    edgeLimitIsValid = true;
+                    edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
+                }
+            }
+            if (oneColumnMode) {
+                return filledOne;
+            }
+            edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null);
+            // start fill from last row again
             rowIndex = mNumRows - 1;
         }
     }
 
-    @Override
-    public final void stripDownTo(int itemIndex) {
-        // because we layout the items in the order that next item is either same row
-        // or next row,  so we can easily find the row range by searching items forward and
-        // backward until we see the row is 0 or mNumRow - 1
-        Location loc = getLocation(itemIndex);
-        if (loc == null) {
-            return;
-        }
-        int firstIndex = getFirstIndex();
-        int lastIndex = getLastIndex();
-        int row = loc.row;
 
-        int endIndex = itemIndex;
-        int endRow = row;
-        while (endRow < mNumRows - 1 && endIndex < lastIndex) {
-            endIndex++;
-            endRow = getLocation(endIndex).row;
-        }
-
-        int startIndex = itemIndex;
-        int startRow = row;
-        while (startRow > 0 && startIndex > firstIndex) {
-            startIndex--;
-            startRow = getLocation(startIndex).row;
-        }
-        // trim information
-        for (int i = firstIndex; i < startIndex; i++) {
-            removeFirst();
-        }
-        for (int i = endIndex; i < lastIndex; i++) {
-            removeLast();
-        }
-    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
index 1c61fa0..9d73470 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
@@ -118,4 +118,11 @@
     public SearchOrbView.Colors getSearchAffordanceColors() {
         return mSearchOrbView.getOrbColors();
     }
+
+    /**
+     * Enables or disables any view animations.
+     */
+    public void enableAnimation(boolean enable) {
+        mSearchOrbView.enableOrbColorAnimation(enable && mSearchOrbView.hasFocus());
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
index f36d1b6..859a188 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
@@ -28,8 +28,45 @@
     private static final String TAG = "GridPresenter";
     private static final boolean DEBUG = false;
 
+    class VerticalGridItemBridgeAdapter extends ItemBridgeAdapter {
+        @Override
+        public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
+            // Only when having an OnItemClickListner, we attach the OnClickListener.
+            if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
+                final View itemView = itemViewHolder.mHolder.view;
+                itemView.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        if (getOnItemClickedListener() != null) {
+                            // Row is always null
+                            getOnItemClickedListener().onItemClicked(itemViewHolder.mItem,
+                                    null);
+                        }
+                        if (getOnItemViewClickedListener() != null) {
+                            // Row is always null
+                            getOnItemViewClickedListener().onItemClicked(
+                                    itemViewHolder.mHolder, itemViewHolder.mItem, null, null);
+                        }
+                    }
+                });
+            }
+        }
+
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
+            if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
+                viewHolder.mHolder.view.setOnClickListener(null);
+            }
+        }
+
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+            viewHolder.itemView.setActivated(true);
+        }
+    }
+
     public static class ViewHolder extends Presenter.ViewHolder {
-        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        ItemBridgeAdapter mItemBridgeAdapter;
         final VerticalGridView mGridView;
         boolean mInitialized;
 
@@ -138,6 +175,7 @@
     public final ViewHolder onCreateViewHolder(ViewGroup parent) {
         ViewHolder vh = createGridViewHolder(parent);
         vh.mInitialized = false;
+        vh.mItemBridgeAdapter = new VerticalGridItemBridgeAdapter();
         initializeGridViewHolder(vh);
         if (!vh.mInitialized) {
             throw new RuntimeException("super.initializeGridViewHolder() must be called");
@@ -200,43 +238,6 @@
                 selectChildView(gridViewHolder, view);
             }
         });
-
-        vh.mItemBridgeAdapter.setAdapterListener(new ItemBridgeAdapter.AdapterListener() {
-            @Override
-            public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
-                // Only when having an OnItemClickListner, we attach the OnClickListener.
-                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
-                    final View itemView = itemViewHolder.mHolder.view;
-                    itemView.setOnClickListener(new View.OnClickListener() {
-                        @Override
-                        public void onClick(View view) {
-                            if (getOnItemClickedListener() != null) {
-                                // Row is always null
-                                getOnItemClickedListener().onItemClicked(itemViewHolder.mItem,
-                                        null);
-                            }
-                            if (getOnItemViewClickedListener() != null) {
-                                // Row is always null
-                                getOnItemViewClickedListener().onItemClicked(
-                                        itemViewHolder.mHolder, itemViewHolder.mItem, null, null);
-                            }
-                        }
-                    });
-                }
-            }
-
-            @Override
-            public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
-                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
-                    viewHolder.mHolder.view.setOnClickListener(null);
-                }
-            }
-
-            @Override
-            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
-                viewHolder.itemView.setActivated(true);
-            }
-        });
     }
 
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java b/v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
index b3a61d8..ce8a55e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
@@ -152,7 +152,9 @@
     public final void loadView(View view, int id) {
         if (mChildStates != null) {
             String key = getSaveStatesKey(id);
-            SparseArray<Parcelable> container = mChildStates.get(key);
+            // Once loaded the state, do not keep the state of child. The child state will
+            // be saved again either when child is offscreen or when the parent is saved.
+            SparseArray<Parcelable> container = mChildStates.remove(key);
             if (container != null) {
                 view.restoreHierarchyState(container);
             }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index 79a2c1a6..ad714e0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -21,6 +21,8 @@
 
 import static android.support.v7.widget.RecyclerView.HORIZONTAL;
 
+import android.view.View;
+
 /**
  * Maintains Window Alignment information of two axis.
  */
@@ -65,6 +67,8 @@
 
         private int mPaddingHigh;
 
+        private boolean mReversedFlow;
+
         private String mName; // for debugging
 
         public Axis(String name) {
@@ -194,19 +198,30 @@
             return mSize - mPaddingLow - mPaddingHigh;
         }
 
-        final public int getSystemScrollPos(boolean isFirst, boolean isLast) {
-            return getSystemScrollPos((int) mScrollCenter, isFirst, isLast);
+        final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) {
+            return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax);
         }
 
-        final public int getSystemScrollPos(int scrollCenter, boolean isFirst, boolean isLast) {
+        final public int getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax) {
             int middlePosition;
-            if (mWindowAlignmentOffset >= 0) {
-                middlePosition = mWindowAlignmentOffset - mPaddingLow;
+            if (!mReversedFlow) {
+                if (mWindowAlignmentOffset >= 0) {
+                    middlePosition = mWindowAlignmentOffset - mPaddingLow;
+                } else {
+                    middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
+                }
+                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+                }
             } else {
-                middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
-            }
-            if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
-                middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+                if (mWindowAlignmentOffset >= 0) {
+                    middlePosition = mSize - mWindowAlignmentOffset - mPaddingLow;
+                } else {
+                    middlePosition = - mWindowAlignmentOffset - mPaddingLow;
+                }
+                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    middlePosition -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+                }
             }
             int clientSize = getClientSize();
             int afterMiddlePosition = clientSize - middlePosition;
@@ -216,30 +231,37 @@
                     (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
                 if (mMaxEdge - mMinEdge <= clientSize) {
                     // total children size is less than view port and we want to align
-                    // both edge:  align first child to left edge of view port
-                    return mMinEdge - mPaddingLow;
+                    // both edge:  align first child to start edge of view port
+                    return mReversedFlow ? mMaxEdge - mPaddingLow - clientSize
+                            : mMinEdge - mPaddingLow;
                 }
             }
             if (!isMinUnknown) {
-                if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 &&
-                        (isFirst || scrollCenter - mMinEdge <= middlePosition)) {
-                    // scroll center is within half of view port size: align the left edge
-                    // of first child to the left edge of view port
+                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
+                     : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
+                        && (isAtMin || scrollCenter - mMinEdge <= middlePosition)) {
+                    // scroll center is within half of view port size: align the start edge
+                    // of first child to the start edge of view port
                     return mMinEdge - mPaddingLow;
                 }
             }
             if (!isMaxUnknown) {
-                if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 &&
-                        (isLast || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
-                    // scroll center is very close to the right edge of view port : align the
-                    // right edge of last children (plus expanded size) to view port's right
-                    return mMaxEdge -mPaddingLow - (clientSize);
+                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
+                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
+                        && (isAtMax || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
+                    // scroll center is very close to the end edge of view port : align the
+                    // end edge of last children (plus expanded size) to view port's end
+                    return mMaxEdge - mPaddingLow - clientSize;
                 }
             }
             // else put scroll center in middle of view port
             return scrollCenter - middlePosition - mPaddingLow;
         }
 
+        final public void setReversedFlow(boolean reversedFlow) {
+            mReversedFlow = reversedFlow;
+        }
+
         @Override
         public String toString() {
             return "center: " + mScrollCenter + " min:" + mMinEdge +
@@ -289,7 +311,7 @@
     public String toString() {
         return new StringBuffer().append("horizontal=")
                 .append(horizontal.toString())
-                .append("vertical=")
+                .append("; vertical=")
                 .append(vertical.toString())
                 .toString();
     }
diff --git a/v17/tests/Android.mk b/v17/tests/Android.mk
new file mode 100644
index 0000000..35de495
--- /dev/null
+++ b/v17/tests/Android.mk
@@ -0,0 +1,40 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR = \
+        $(LOCAL_PATH)/res \
+        $(LOCAL_PATH)/../leanback/res
+LOCAL_AAPT_FLAGS := \
+        --auto-add-overlay \
+        --extra-packages android.support.v17.leanback
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        android-support-v4 \
+        android-support-v7-recyclerview \
+        android-support-v17-leanback
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := AndroidLeanbackTests
+
+include $(BUILD_PACKAGE)
diff --git a/v17/tests/AndroidManifest.xml b/v17/tests/AndroidManifest.xml
new file mode 100644
index 0000000..364c22531
--- /dev/null
+++ b/v17/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v17.leanback.tests">
+    <uses-sdk android:minSdkVersion="17"/>
+
+    <!--
+        This declares that this application uses the instrumentation test runner targeting
+        the package of android.support.v17.leanback.tests.  To run the tests use the command:
+        "adb shell am instrument -w android.support.v17.leanback.tests/android.test.InstrumentationTestRunner"
+    -->
+    <instrumentation
+        android:targetPackage="android.support.v17.leanback.tests"
+        android:name="android.test.InstrumentationTestRunner" />
+
+    <application
+        android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="android.support.v17.leanback.widget.GridActivity"
+            android:exported="true" />
+
+    </application>
+
+</manifest>
diff --git a/v17/tests/README.txt b/v17/tests/README.txt
new file mode 100644
index 0000000..fd1fb56
--- /dev/null
+++ b/v17/tests/README.txt
@@ -0,0 +1,7 @@
+Test project for support leanback.
+
+INSTALLATION
+adb install -r AndroidLeanbackTests.apk
+
+RUN TESTS
+adb shell am instrument -w android.support.v17.leanback.tests/android.test.InstrumentationTestRunner
\ No newline at end of file
diff --git a/v17/tests/res/layout/horizontal_grid.xml b/v17/tests/res/layout/horizontal_grid.xml
new file mode 100644
index 0000000..5119f79
--- /dev/null
+++ b/v17/tests/res/layout/horizontal_grid.xml
@@ -0,0 +1,23 @@
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <android.support.v17.leanback.widget.HorizontalGridView
+      android:id="@+id/gridview"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:clipToPadding="false"
+      android:focusable="true"
+      android:focusableInTouchMode="true"
+      android:background="#00ffff"
+      lb:horizontalMargin="12dip"
+      lb:verticalMargin="24dip"
+      lb:numberOfRows="3"
+      lb:rowHeight="150dip"
+      android:paddingBottom="12dip"
+      android:paddingLeft="12dip"
+      android:paddingRight="12dip"
+      android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/horizontal_grid_testredundantappendremove2.xml b/v17/tests/res/layout/horizontal_grid_testredundantappendremove2.xml
new file mode 100644
index 0000000..b539f3c
--- /dev/null
+++ b/v17/tests/res/layout/horizontal_grid_testredundantappendremove2.xml
@@ -0,0 +1,23 @@
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <android.support.v17.leanback.widget.HorizontalGridView
+      android:id="@+id/gridview"
+      android:layout_width="960dp"
+      android:layout_height="492dp"
+      android:clipToPadding="false"
+      android:focusable="true"
+      android:focusableInTouchMode="true"
+      android:background="#00ffff"
+      lb:horizontalMargin="12dip"
+      lb:verticalMargin="24dip"
+      lb:numberOfRows="3"
+      lb:rowHeight="150dip"
+      android:paddingBottom="12dip"
+      android:paddingLeft="12dip"
+      android:paddingRight="12dip"
+      android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/vertical_grid.xml b/v17/tests/res/layout/vertical_grid.xml
new file mode 100644
index 0000000..85e1d46
--- /dev/null
+++ b/v17/tests/res/layout/vertical_grid.xml
@@ -0,0 +1,23 @@
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <android.support.v17.leanback.widget.VerticalGridView
+      android:id="@+id/gridview"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:clipToPadding="false"
+      android:focusable="true"
+      android:focusableInTouchMode="true"
+      android:background="#00ffff"
+      lb:horizontalMargin="12dip"
+      lb:verticalMargin="24dip"
+      lb:numberOfColumns="3"
+      lb:columnWidth="150dip"
+      android:paddingBottom="12dip"
+      android:paddingLeft="12dip"
+      android:paddingRight="12dip"
+      android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/vertical_grid_testredundantappendremove.xml b/v17/tests/res/layout/vertical_grid_testredundantappendremove.xml
new file mode 100644
index 0000000..4ce56c5
--- /dev/null
+++ b/v17/tests/res/layout/vertical_grid_testredundantappendremove.xml
@@ -0,0 +1,23 @@
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    >
+  <android.support.v17.leanback.widget.VerticalGridView
+      android:id="@+id/gridview"
+      android:layout_width="960dp"
+      android:layout_height="492dp"
+      android:clipToPadding="false"
+      android:focusable="true"
+      android:focusableInTouchMode="true"
+      android:background="#00ffff"
+      lb:horizontalMargin="12dip"
+      lb:verticalMargin="24dip"
+      lb:numberOfColumns="3"
+      lb:columnWidth="150dip"
+      android:paddingBottom="12dip"
+      android:paddingLeft="12dip"
+      android:paddingRight="12dip"
+      android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
new file mode 100644
index 0000000..ee6f020
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
@@ -0,0 +1,226 @@
+/*
+ * 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.support.v17.leanback.tests.R;
+import android.support.v7.widget.RecyclerView;
+import android.support.v17.leanback.widget.BaseGridView;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * @hide from javadoc
+ */
+public class GridActivity extends Activity {
+    private static final String TAG = "GridActivity";
+
+    public static final String EXTRA_LAYOUT_RESOURCE_ID = "layoutResourceId";
+    public static final String EXTRA_NUM_ITEMS = "numItems";
+    public static final String EXTRA_ITEMS = "items";
+    public static final String EXTRA_STAGGERED = "staggered";
+    public static final String SELECT_ACTION = "android.test.leanback.widget.SELECT";
+
+    static final int DEFAULT_NUM_ITEMS = 100;
+    static final boolean DEFAULT_STAGGERED = true;
+
+    private static final boolean DEBUG = false;
+
+    int mLayoutId;
+    int mOrientation;
+    int mNumItems;
+    boolean mStaggered;
+
+    int[] mGridViewLayoutSize;
+    BaseGridView mGridView;
+    int[] mItemLengths;
+
+    private int mBoundCount;
+
+    private View createView() {
+
+        View view = getLayoutInflater().inflate(mLayoutId, null, false);
+        mGridView = (BaseGridView) view.findViewById(R.id.gridview);
+        mOrientation = mGridView instanceof HorizontalGridView ? BaseGridView.HORIZONTAL :
+                BaseGridView.VERTICAL;
+        mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
+        mGridView.setWindowAlignmentOffsetPercent(35);
+        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                if (DEBUG) Log.d(TAG, "onChildSelected position=" + position +  " id="+id);
+            }
+        });
+        return view;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Intent intent = getIntent();
+
+        mLayoutId = intent.getIntExtra(EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        mStaggered = intent.getBooleanExtra(EXTRA_STAGGERED, DEFAULT_STAGGERED);
+        mItemLengths = intent.getIntArrayExtra(EXTRA_ITEMS);
+        if (mItemLengths == null) {
+            mNumItems = intent.getIntExtra(EXTRA_NUM_ITEMS, DEFAULT_NUM_ITEMS);
+            mItemLengths = new int[mNumItems];
+            for (int i = 0; i < mItemLengths.length; i++) {
+                if (mOrientation == BaseGridView.HORIZONTAL) {
+                    mItemLengths[i] = mStaggered ? (int)(Math.random() * 180) + 180 : 240;
+                } else {
+                    mItemLengths[i] = mStaggered ? (int)(Math.random() * 120) + 120 : 160;
+                }
+            }
+        } else {
+            mNumItems = mItemLengths.length;
+        }
+
+        super.onCreate(savedInstanceState);
+
+        if (DEBUG) Log.v(TAG, "onCreate " + this);
+
+        RecyclerView.Adapter adapter = new MyAdapter();
+
+        View view = createView();
+
+        mGridView.setAdapter(new MyAdapter());
+        setContentView(view);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (DEBUG) Log.v(TAG, "onNewIntent " + intent+ " "+this);
+        if (intent.getAction().equals(SELECT_ACTION)) {
+            int position = intent.getIntExtra("SELECT_POSITION", -1);
+            if (position >= 0) {
+                mGridView.setSelectedPosition(position);
+            }
+        }
+        super.onNewIntent(intent);
+    }
+
+    private OnFocusChangeListener mItemFocusChangeListener = new OnFocusChangeListener() {
+
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            if (hasFocus) {
+                v.setBackgroundColor(Color.YELLOW);
+            } else {
+                v.setBackgroundColor(Color.LTGRAY);
+            }
+        }
+    };
+
+    void resetBoundCount() {
+        mBoundCount = 0;
+    }
+
+    int getBoundCount() {
+       return mBoundCount;
+    }
+
+    void swap(int index1, int index2) {
+        if (index1 == index2) {
+            return;
+        } else if (index1 > index2) {
+            int index = index1;
+            index1 = index2;
+            index2 = index;
+        }
+        int value = mItemLengths[index1];
+        mItemLengths[index1] = mItemLengths[index2];
+        mItemLengths[index2] = value;
+        mGridView.getAdapter().notifyItemMoved(index1, index2);
+        mGridView.getAdapter().notifyItemMoved(index2 - 1, index1);
+    }
+
+    void changeArraySize(int length) {
+        mNumItems = length;
+        mGridView.getAdapter().notifyDataSetChanged();
+    }
+
+    int[] removeItems(int index, int length) {
+        int[] removed = new int[length];
+        System.arraycopy(mItemLengths, index, removed, 0, length);
+        System.arraycopy(mItemLengths, index + length, mItemLengths, index,
+                mNumItems - index - length);
+        mNumItems -= length;
+        mGridView.getAdapter().notifyItemRangeRemoved(index, length);
+        return removed;
+    }
+
+    void addItems(int index, int[] items) {
+        int length = items.length;
+        System.arraycopy(mItemLengths, index, mItemLengths, index + length, mNumItems - index);
+        System.arraycopy(items, 0, mItemLengths, index, length);
+        mNumItems += length;
+        mGridView.getAdapter().notifyItemRangeInserted(index, length);
+    }
+
+    class MyAdapter extends RecyclerView.Adapter {
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            if (DEBUG) Log.v(TAG, "createViewHolder " + viewType);
+            TextView textView = new TextView(parent.getContext());
+            textView.setTextColor(Color.BLACK);
+            textView.setFocusable(true);
+            textView.setFocusableInTouchMode(true);
+            textView.setOnFocusChangeListener(mItemFocusChangeListener);
+            return new ViewHolder(textView);
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) {
+            if (DEBUG) Log.v(TAG, "bindViewHolder " + position + " " + baseHolder);
+            mBoundCount++;
+            ViewHolder holder = (ViewHolder) baseHolder;
+            ((TextView) holder.itemView).setText("Item "+position);
+            holder.itemView.setBackgroundColor(Color.LTGRAY);
+            if (mOrientation == BaseGridView.HORIZONTAL) {
+                holder.itemView.setLayoutParams(new ViewGroup.MarginLayoutParams(
+                        mItemLengths[position], 80));
+            } else {
+                holder.itemView.setLayoutParams(new ViewGroup.MarginLayoutParams(
+                        240, mItemLengths[position]));
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return mNumItems;
+        }
+    }
+
+    static class ViewHolder extends RecyclerView.ViewHolder {
+
+        public ViewHolder(View v) {
+            super(v);
+        }
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridTest.java
new file mode 100644
index 0000000..40deed3
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.test.AndroidTestCase;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Base class for testing Grid algorithm
+ * @hide
+ */
+public abstract class GridTest extends AndroidTestCase {
+
+    static class Provider implements Grid.Provider {
+
+        int[] mItems;
+        int mCount;
+        int[] mEdges;
+
+        Provider(int[] items) {
+            mItems = items;
+            mCount = items.length;
+            mEdges = new int[mCount];
+        }
+
+        @Override
+        public int getCount() {
+            return mCount;
+        }
+
+        @Override
+        public int createItem(int index, boolean append, Object[] item) {
+            return mItems[index];
+        }
+
+        @Override
+        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
+            if (edge == Integer.MAX_VALUE || edge == Integer.MIN_VALUE) {
+                // initialize edge for first item added
+                edge = 0;
+            }
+            mEdges[index] = edge;
+        }
+
+        @Override
+        public void removeItem(int index) {
+        }
+
+        @Override
+        public int getEdge(int index) {
+            return mEdges[index];
+        }
+
+        @Override
+        public int getSize(int index) {
+            return mItems[index];
+        }
+
+        void scroll(int distance) {
+            for (int i= 0; i < mEdges.length; i++) {
+                mEdges[i] -= distance;
+            }
+        }
+    }
+
+    Provider mProvider;
+
+    static String dump(Grid grid) {
+        StringWriter w = new StringWriter();
+        grid.debugPrint(new PrintWriter(w));
+        return w.toString();
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
new file mode 100644
index 0000000..d76d389
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -0,0 +1,503 @@
+/*
+ * 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.support.v17.leanback.tests.R;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.app.Instrumentation;
+import android.content.Intent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * @hide from javadoc
+ */
+public class GridWidgetTest extends ActivityInstrumentationTestCase2<GridActivity> {
+
+    protected GridActivity mActivity;
+    protected Instrumentation mInstrumentation;
+    protected BaseGridView mGridView;
+    protected GridLayoutManager mLayoutManager;
+    protected int mOrientation;
+    protected int mNumRows;
+
+    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
+        public int compare(View lhs, View rhs) {
+            if (mOrientation == BaseGridView.HORIZONTAL) {
+                return lhs.getLeft() - rhs.getLeft();
+            } else {
+                return lhs.getTop() - rhs.getTop();
+            }
+        };
+    };
+
+    /**
+     * Verify margins between items on same row are same.
+     */
+    private final Runnable mVerifyLayout = new Runnable() {
+        @Override
+        public void run() {
+            verifyMargin();
+        }
+    };
+
+    public GridWidgetTest() {
+        super("android.support.v17.leanback.tests", GridActivity.class);
+    }
+
+    /**
+     * Wait for grid view stop scroll and optionally verify state of grid view.
+     */
+    protected void waitForScrollIdle(Runnable verify) throws Throwable {
+        while (mGridView.getLayoutManager().isSmoothScrolling() ||
+                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+            if (verify != null) {
+                runTestOnUiThread(verify);
+            }
+        }
+    }
+
+    /**
+     * Wait for grid view stop animation and optionally verify state of grid view.
+     */
+    protected void waitForTransientStateGone(Runnable verify) throws Throwable {
+        do {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+            if (verify != null) {
+                runTestOnUiThread(verify);
+            }
+        } while (mGridView.hasTransientState());
+    }
+
+    /**
+     * Wait for grid view stop scroll.
+     */
+    protected void waitForScrollIdle() throws Throwable {
+        waitForScrollIdle(null);
+    }
+
+    /**
+     * Scrolls using given key.
+     */
+    protected void scroll(int key, Runnable verify) throws Throwable {
+        do {
+            if (verify != null) {
+                runTestOnUiThread(verify);
+            }
+            sendRepeatedKeys(10, key);
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+        } while (mGridView.getLayoutManager().isSmoothScrolling() ||
+                mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
+    }
+
+    protected void scrollToBegin(Runnable verify) throws Throwable {
+        int key;
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        } else {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        }
+        scroll(key, verify);
+    }
+
+    protected void scrollToEnd(Runnable verify) throws Throwable {
+        int key;
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            }
+        } else {
+            key = KeyEvent.KEYCODE_DPAD_DOWN;
+        }
+        scroll(key, verify);
+    }
+
+    /**
+     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
+     */
+    protected View[][] sortByRows() {
+        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
+        ArrayList<Integer> rowLocations = new ArrayList();
+        for (int i = 0; i < mGridView.getChildCount(); i++) {
+            View v = mGridView.getChildAt(i);
+            int rowLocation;
+            if (mOrientation == BaseGridView.HORIZONTAL) {
+                rowLocation = v.getTop();
+            } else {
+                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL ?
+                    v.getRight() : v.getLeft();
+            }
+            ArrayList<View> views = rows.get(rowLocation);
+            if (views == null) {
+                views = new ArrayList<View>();
+                rows.put(rowLocation, views);
+                rowLocations.add(rowLocation);
+            }
+            views.add(v);
+        }
+        Object[] sortedLocations = rowLocations.toArray();
+        Arrays.sort(sortedLocations);
+        if (mNumRows != rows.size()) {
+            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
+        }
+        View[][] sorted = new View[rows.size()][];
+        for (int i = 0; i < rowLocations.size(); i++) {
+            Integer rowLocation = rowLocations.get(i);
+            ArrayList<View> arr = rows.get(rowLocation);
+            View[] views = arr.toArray(new View[arr.size()]);
+            Arrays.sort(views, mRowSortComparator);
+            sorted[i] = views;
+        }
+        return sorted;
+    }
+
+    protected void verifyMargin() {
+        View[][] sorted = sortByRows();
+        for (int row = 0; row < sorted.length; row++) {
+            View[] views = sorted[row];
+            int margin = -1;
+            for (int i = 1; i < views.length; i++) {
+                if (mOrientation == BaseGridView.HORIZONTAL) {
+                    if (i == 1) {
+                        margin = views[i].getLeft() - views[i - 1].getRight();
+                    } else {
+                        assertEquals(margin, views[i].getLeft() - views[i - 1].getRight());
+                    }
+                } else {
+                    if (i == 1) {
+                        margin = views[i].getTop() - views[i - 1].getBottom();
+                    } else {
+                        assertEquals(margin, views[i].getTop() - views[i - 1].getBottom());
+                    }
+                }
+            }
+        }
+    }
+
+    protected void verifyBeginAligned() {
+        View[][] sorted = sortByRows();
+        int alignedLocation = 0;
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                for (int i = 0; i < sorted.length; i++) {
+                    if (i == 0) {
+                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
+                    } else {
+                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
+                    }
+                }
+            } else {
+                for (int i = 0; i < sorted.length; i++) {
+                    if (i == 0) {
+                        alignedLocation = sorted[i][0].getLeft();
+                    } else {
+                        assertEquals(alignedLocation, sorted[i][0].getLeft());
+                    }
+                }
+            }
+        } else {
+            for (int i = 0; i < sorted.length; i++) {
+                if (i == 0) {
+                    alignedLocation = sorted[i][0].getTop();
+                } else {
+                    assertEquals(alignedLocation, sorted[i][0].getTop());
+                }
+            }
+        }
+    }
+
+    protected int[] getEndEdges() {
+        View[][] sorted = sortByRows();
+        int[] edges = new int[sorted.length];
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                for (int i = 0; i < sorted.length; i++) {
+                    edges[i] = sorted[i][0].getLeft();
+                }
+            } else {
+                for (int i = 0; i < sorted.length; i++) {
+                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
+                }
+            }
+        } else {
+            for (int i = 0; i < sorted.length; i++) {
+                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
+            }
+        }
+        return edges;
+    }
+
+    protected void verifyEdgesSame(int[] edges, int[] edges2) {
+        assertEquals(edges.length, edges2.length);
+        for (int i = 0; i < edges.length; i++) {
+            assertEquals(edges[i], edges2[i]);
+        }
+    }
+
+    protected void verifyBoundCount(int count) {
+        if (mActivity.getBoundCount() != count) {
+            StringBuffer b = new StringBuffer();
+            b.append("ItemsLength: ");
+            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
+                b.append(mActivity.mItemLengths[i]).append(",");
+            }
+            assertEquals("Bound count does not match, ItemsLengths: "+ b,
+                    count, mActivity.getBoundCount());
+        }
+    }
+
+    public void testThreeRowHorizontalBasic() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+        verifyBoundCount(100);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    public void testThreeColumnVerticalBasic() 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, 200);
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+        verifyBoundCount(200);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    public void testRedundantAppendRemove() throws Throwable {
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid_testredundantappendremove);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
+                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
+                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
+                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
+                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
+                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
+                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
+                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
+                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
+                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
+                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+
+        verifyBoundCount(200);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    public void testRedundantAppendRemove2() throws Throwable {
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid_testredundantappendremove2);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
+                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
+                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
+                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
+                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
+                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
+
+        // test append without staggered result cache
+        scrollToEnd(mVerifyLayout);
+
+        verifyBoundCount(100);
+        int[] endEdges = getEndEdges();
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+
+        // now test append with staggered result cache
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mActivity.changeArraySize(3);
+            }
+        });
+        Thread.sleep(500);
+        assertEquals("Staggerd cache should be kept as is when no item size change",
+                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
+
+        mActivity.resetBoundCount();
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mActivity.changeArraySize(100);
+            }
+        });
+        Thread.sleep(500);
+
+        scrollToEnd(mVerifyLayout);
+        verifyBoundCount(100);
+
+        // we should get same aligned end edges
+        int[] endEdges2 = getEndEdges();
+        verifyEdgesSame(endEdges, endEdges2);
+    }
+
+    public void testItemMovedHorizontal() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        mActivity.swap(150, 152);
+        waitForTransientStateGone(null);
+
+        runTestOnUiThread(mVerifyLayout);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    public void testItemMovedVertical() 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, 200);
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        mActivity.swap(150, 152);
+        waitForTransientStateGone(null);
+
+        runTestOnUiThread(mVerifyLayout);
+
+        scrollToEnd(mVerifyLayout);
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    public void testItemAddRemoveHorizontal() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        mGridView = mActivity.mGridView;
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+        int[] endEdges = getEndEdges();
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        int[] removedItems = mActivity.removeItems(151, 4);
+        waitForTransientStateGone(null);
+
+        scrollToEnd(mVerifyLayout);
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+
+        mActivity.addItems(151, removedItems);
+        waitForTransientStateGone(null);
+        scrollToEnd(mVerifyLayout);
+
+        // we should get same aligned end edges
+        int[] endEdges2 = getEndEdges();
+        verifyEdgesSame(endEdges, endEdges2);
+
+        scrollToBegin(mVerifyLayout);
+        verifyBeginAligned();
+    }
+
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java b/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java
new file mode 100644
index 0000000..86e2517
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.support.v17.leanback.app.HeadersFragment;
+import android.test.AndroidTestCase;
+import android.widget.FrameLayout;
+import android.view.View;
+
+import junit.framework.Assert;
+
+public class PresenterTest extends AndroidTestCase {
+
+    public void testZoomFactors() throws Throwable {
+        new ListRowPresenter(FocusHighlight.ZOOM_FACTOR_SMALL);
+        new ListRowPresenter(FocusHighlight.ZOOM_FACTOR_MEDIUM);
+        new ListRowPresenter(FocusHighlight.ZOOM_FACTOR_LARGE);
+        new ListRowPresenter(FocusHighlight.ZOOM_FACTOR_XSMALL);
+        try {
+            new ListRowPresenter(100);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalArgumentException exception) {
+        }
+    }
+
+    private void testHeaderPresenter(RowHeaderPresenter p) {
+        int expectedVisibility;
+        Presenter.ViewHolder vh = p.onCreateViewHolder(new FrameLayout(getContext()));
+        p.onBindViewHolder(vh, null);
+        expectedVisibility = p.isNullItemVisibilityGone() ? View.GONE : View.VISIBLE;
+        Assert.assertTrue(vh.view.getVisibility() == expectedVisibility);
+        p.onBindViewHolder(vh, new Row(null));
+        Assert.assertTrue(vh.view.getVisibility() == expectedVisibility);
+        p.onBindViewHolder(vh, new Row(new HeaderItem("")));
+        Assert.assertTrue(vh.view.getVisibility() == View.VISIBLE);
+    }
+
+    public void testHeaderPresenter() throws Throwable {
+        HeadersFragment hf = new HeadersFragment();
+        PresenterSelector ps = hf.getPresenterSelector();
+        Presenter p = ps.getPresenter(new Object());
+        Assert.assertTrue(p instanceof RowHeaderPresenter);
+        Assert.assertFalse(((RowHeaderPresenter) p).isNullItemVisibilityGone());
+        testHeaderPresenter((RowHeaderPresenter) p);
+
+        ListRowPresenter lrp = new ListRowPresenter();
+        Assert.assertTrue(lrp.getHeaderPresenter() instanceof RowHeaderPresenter);
+        RowHeaderPresenter rhp = (RowHeaderPresenter) lrp.getHeaderPresenter();
+        Assert.assertTrue(rhp.isNullItemVisibilityGone());
+        testHeaderPresenter(rhp);
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java b/v17/tests/src/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java
new file mode 100644
index 0000000..76456c4
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Testing StaggeredGridDefault algorithm
+ * @hide
+ */
+public class StaggeredGridDefaultTest extends GridTest {
+
+    StaggeredGridDefault mStaggeredGrid;
+
+    public void testWhenToFillNextRow() throws Throwable {
+        mProvider = new Provider(new int[]{100, 100, 100, 100, 40, 100, 100, 30, 100});
+
+        // layout first 8 items then all items
+        mStaggeredGrid = new StaggeredGridDefault();
+        mStaggeredGrid.setNumRows(3);
+        mStaggeredGrid.setMargin(20);
+        mStaggeredGrid.setProvider(mProvider);
+        mStaggeredGrid.appendVisibleItems(210);
+        assertEquals(dump(mStaggeredGrid) + " Should fill 8 items",
+                8, mStaggeredGrid.mLocations.size());
+        // 2nd fill rest
+        mStaggeredGrid.appendVisibleItems(100000);
+        assertEquals(dump(mStaggeredGrid) + " Should fill 9 items",
+                9, mStaggeredGrid.mLocations.size());
+        int row_result1 = mStaggeredGrid.getLocation(8).row;
+        assertEquals(dump(mStaggeredGrid) + " last item should be placed on row 1",
+                1, row_result1);
+
+        // layout all items together
+        mStaggeredGrid = new StaggeredGridDefault();
+        mStaggeredGrid.setNumRows(3);
+        mStaggeredGrid.setMargin(20);
+        mStaggeredGrid.setProvider(mProvider);
+        mStaggeredGrid.appendVisibleItems(100000);
+        assertEquals(dump(mStaggeredGrid) + " should fill 9 items",
+                9, mStaggeredGrid.mLocations.size());
+        int row_result2 = mStaggeredGrid.getLocation(8).row;
+
+        assertEquals(dump(mStaggeredGrid) + " last item should be placed on row 1",
+                1, row_result2);
+    }
+}
diff --git a/v4/Android.mk b/v4/Android.mk
index dbebab4..855b4f2 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_MODULE := android-support-v4-donut
 LOCAL_SDK_VERSION := 4
 LOCAL_SRC_FILES := $(call all-java-files-under, donut)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # -----------------------------------------------------------------------
@@ -153,7 +154,7 @@
 
 # -----------------------------------------------------------------------
 
-# A helper sub-library that makes direct use of the upcoming API.
+# A helper sub-library that makes direct use of V20 APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4-api20
 LOCAL_SDK_VERSION := 20
@@ -163,25 +164,36 @@
 
 # -----------------------------------------------------------------------
 
-# A helper sub-library that makes direct use of the upcoming API.
-# TODO: Apply a real name and SDK version when available
+# A helper sub-library that makes direct use of Lollipop APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # -----------------------------------------------------------------------
 
+# A helper sub-library that makes direct use of V22 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api22
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api22)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api21
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # Here is the final static library that apps can link against.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4
 LOCAL_SDK_VERSION := 4
 
+LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
+
 LOCAL_SRC_FILES := $(call all-java-files-under, java) \
     $(call all-Iaidl-files-under, java)
 
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api21
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api22
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
index e6025e1..ef41045 100644
--- a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -29,6 +29,7 @@
 import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 class FragmentTransitionCompat21 {
@@ -44,7 +45,7 @@
     }
 
     public static Object captureExitingViews(Object exitTransition, View root,
-            ArrayList<View> viewList, Map<String, View> namedViews) {
+            ArrayList<View> viewList, Map<String, View> namedViews, View nonExistentView) {
         if (exitTransition != null) {
             captureTransitioningViews(viewList, root);
             if (namedViews != null) {
@@ -53,6 +54,7 @@
             if (viewList.isEmpty()) {
                 exitTransition = null;
             } else {
+                viewList.add(nonExistentView);
                 addTargets((Transition) exitTransition, viewList);
             }
         }
@@ -130,6 +132,8 @@
                                     if (enterTransition != null) {
                                         captureTransitioningViews(enteringViews, fragmentView);
                                         enteringViews.removeAll(renamedViews.values());
+                                        enteringViews.add(nonExistentView);
+                                        enterTransition.removeTarget(nonExistentView);
                                         addTargets(enterTransition, enteringViews);
                                     }
                                 }
@@ -304,22 +308,71 @@
         }
     }
 
+    /**
+     * This method removes the views from transitions that target ONLY those views.
+     * The views list should match those added in addTargets and should contain
+     * one view that is not in the view hierarchy (state.nonExistentView).
+     */
     public static void removeTargets(Object transitionObject, ArrayList<View> views) {
         Transition transition = (Transition) transitionObject;
-        int numViews = views.size();
-        for (int i = 0; i < numViews; i++) {
-            transition.removeTarget(views.get(i));
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                removeTargets(child, views);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (targets != null && targets.size() == views.size() &&
+                    targets.containsAll(views)) {
+                // We have an exact match. We must have added these earlier in addTargets
+                for (int i = views.size() - 1; i >= 0; i--) {
+                    transition.removeTarget(views.get(i));
+                }
+            }
         }
     }
 
+    /**
+     * This method adds views as targets to the transition, but only if the transition
+     * doesn't already have a target. It is best for views to contain one View object
+     * that does not exist in the view hierarchy (state.nonExistentView) so that
+     * when they are removed later, a list match will suffice to remove the targets.
+     * Otherwise, if you happened to have targeted the exact views for the transition,
+     * the removeTargets call will remove them unexpectedly.
+     */
     public static void addTargets(Object transitionObject, ArrayList<View> views) {
         Transition transition = (Transition) transitionObject;
-        int numViews = views.size();
-        for (int i = 0; i < numViews; i++) {
-            transition.addTarget(views.get(i));
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                addTargets(child, views);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (isNullOrEmpty(targets)) {
+                // We can just add the target views
+                int numViews = views.size();
+                for (int i = 0; i < numViews; i++) {
+                    transition.addTarget(views.get(i));
+                }
+            }
         }
     }
 
+    private static boolean hasSimpleTarget(Transition transition) {
+        return !isNullOrEmpty(transition.getTargetIds()) ||
+                !isNullOrEmpty(transition.getTargetNames()) ||
+                !isNullOrEmpty(transition.getTargetTypes());
+    }
+
+    private static boolean isNullOrEmpty(List list) {
+        return list == null || list.isEmpty();
+    }
+
     public interface ViewRetriever {
         View getView();
     }
diff --git a/v4/api21/android/support/v4/app/NotificationCompatApi21.java b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
index 94f6776..a16b2a2 100644
--- a/v4/api21/android/support/v4/app/NotificationCompatApi21.java
+++ b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.widget.RemoteViews;
 
 import java.util.ArrayList;
@@ -42,6 +43,15 @@
     public static final String CATEGORY_RECOMMENDATION = Notification.CATEGORY_RECOMMENDATION;
     public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS;
 
+    private static final String KEY_AUTHOR = "author";
+    private static final String KEY_TEXT = "text";
+    private static final String KEY_MESSAGES = "messages";
+    private static final String KEY_REMOTE_INPUT = "remote_input";
+    private static final String KEY_ON_REPLY = "on_reply";
+    private static final String KEY_ON_READ = "on_read";
+    private static final String KEY_PARTICIPANTS = "participants";
+    private static final String KEY_TIMESTAMP = "timestamp";
+
     public static class Builder implements NotificationBuilderWithBuilderAccessor,
             NotificationBuilderWithActions {
         private Notification.Builder b;
@@ -113,4 +123,100 @@
     public static String getCategory(Notification notif) {
         return notif.category;
     }
+
+    static Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+        if (uc == null) {
+            return null;
+        }
+        Bundle b = new Bundle();
+        String author = null;
+        if (uc.getParticipants() != null && uc.getParticipants().length > 1) {
+            author = uc.getParticipants()[0];
+        }
+        Parcelable[] messages = new Parcelable[uc.getMessages().length];
+        for (int i = 0; i < messages.length; i++) {
+            Bundle m = new Bundle();
+            m.putString(KEY_TEXT, uc.getMessages()[i]);
+            m.putString(KEY_AUTHOR, author);
+            messages[i] = m;
+        }
+        b.putParcelableArray(KEY_MESSAGES, messages);
+        RemoteInputCompatBase.RemoteInput remoteInput = uc.getRemoteInput();
+        if (remoteInput != null) {
+            b.putParcelable(KEY_REMOTE_INPUT, fromCompatRemoteInput(remoteInput));
+        }
+        b.putParcelable(KEY_ON_REPLY, uc.getReplyPendingIntent());
+        b.putParcelable(KEY_ON_READ, uc.getReadPendingIntent());
+        b.putStringArray(KEY_PARTICIPANTS, uc.getParticipants());
+        b.putLong(KEY_TIMESTAMP, uc.getLatestTimestamp());
+        return b;
+    }
+
+    static NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+            Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        if (b == null) {
+            return null;
+        }
+        Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
+        String[] messages = null;
+        if (parcelableMessages != null) {
+            String[] tmp = new String[parcelableMessages.length];
+            boolean success = true;
+            for (int i = 0; i < tmp.length; i++) {
+                if (!(parcelableMessages[i] instanceof Bundle)) {
+                    success = false;
+                    break;
+                }
+                tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
+                if (tmp[i] == null) {
+                    success = false;
+                    break;
+                }
+            }
+            if (success) {
+                messages = tmp;
+            } else {
+                return null;
+            }
+        }
+
+        PendingIntent onRead = b.getParcelable(KEY_ON_READ);
+        PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
+
+        android.app.RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
+
+        String[] participants = b.getStringArray(KEY_PARTICIPANTS);
+        if (participants == null || participants.length != 1) {
+            return null;
+        }
+
+
+        return factory.build(
+                messages,
+                remoteInput != null ? toCompatRemoteInput(remoteInput, remoteInputFactory) : null,
+                onReply,
+                onRead,
+                participants, b.getLong(KEY_TIMESTAMP));
+    }
+
+    private static android.app.RemoteInput fromCompatRemoteInput(
+            RemoteInputCompatBase.RemoteInput src) {
+        return new android.app.RemoteInput.Builder(src.getResultKey())
+                .setLabel(src.getLabel())
+                .setChoices(src.getChoices())
+                .setAllowFreeFormInput(src.getAllowFreeFormInput())
+                .addExtras(src.getExtras())
+                .build();
+    }
+
+    private static RemoteInputCompatBase.RemoteInput toCompatRemoteInput(
+            android.app.RemoteInput remoteInput,
+            RemoteInputCompatBase.RemoteInput.Factory factory) {
+        return factory.build(remoteInput.getResultKey(),
+                remoteInput.getLabel(),
+                remoteInput.getChoices(),
+                remoteInput.getAllowFreeFormInput(),
+                remoteInput.getExtras());
+    }
 }
diff --git a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
index 645b633..2272c02 100644
--- a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
+++ b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
@@ -17,11 +17,18 @@
 package android.support.v4.content.res;
 
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
 import android.graphics.drawable.Drawable;
 
 class ResourcesCompatApi21 {
-    public static Drawable getDrawable(Resources res, int id, Theme theme) {
+    public static Drawable getDrawable(Resources res, int id, Theme theme)
+            throws NotFoundException {
         return res.getDrawable(id, theme);
     }
+
+    public static Drawable getDrawableForDensity(Resources res, int id, int density, Theme theme)
+            throws NotFoundException {
+        return res.getDrawableForDensity(id, density, theme);
+    }
 }
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java
deleted file mode 100644
index 67d4e26..0000000
--- a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.graphics.drawable;
-
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-
-/**
- * Implementation of drawable compatibility that can call L APIs.
- */
-class DrawableCompatL {
-
-    public static void setHotspot(Drawable drawable, float x, float y) {
-        drawable.setHotspot(x, y);
-    }
-
-    public static void setHotspotBounds(Drawable drawable, int left, int top,
-            int right, int bottom) {
-        drawable.setHotspotBounds( left, top, right, bottom);
-    }
-
-    public static void setTint(Drawable drawable, int tint) {
-        drawable.setTint(tint);
-    }
-
-    public static void setTintList(Drawable drawable, ColorStateList tint) {
-        drawable.setTintList(tint);
-    }
-
-    public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
-        drawable.setTintMode(tintMode);
-    }
-
-}
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
new file mode 100644
index 0000000..8efc669
--- /dev/null
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+
+/**
+ * Implementation of drawable compatibility that can call L APIs.
+ */
+class DrawableCompatLollipop {
+
+    public static void setHotspot(Drawable drawable, float x, float y) {
+        drawable.setHotspot(x, y);
+    }
+
+    public static void setHotspotBounds(Drawable drawable, int left, int top,
+            int right, int bottom) {
+        drawable.setHotspotBounds( left, top, right, bottom);
+    }
+
+    public static void setTint(Drawable drawable, int tint) {
+        if (drawable instanceof DrawableWrapperLollipop) {
+            // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
+            // functionality instead
+            DrawableCompatBase.setTint(drawable, tint);
+        } else {
+            // Else, we'll use the framework API
+            drawable.setTint(tint);
+        }
+    }
+
+    public static void setTintList(Drawable drawable, ColorStateList tint) {
+        if (drawable instanceof DrawableWrapperLollipop) {
+            // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
+            // functionality instead
+            DrawableCompatBase.setTintList(drawable, tint);
+        } else {
+            // Else, we'll use the framework API
+            drawable.setTintList(tint);
+        }
+    }
+
+    public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
+        if (drawable instanceof GradientDrawable) {
+            // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
+            // functionality instead
+            DrawableCompatBase.setTintMode(drawable, tintMode);
+        } else {
+            // Else, we'll use the framework API
+            drawable.setTintMode(tintMode);
+        }
+    }
+
+    public static Drawable wrapForTinting(Drawable drawable) {
+        if (drawable instanceof GradientDrawable) {
+            // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
+            // functionality instead
+            return new DrawableWrapperLollipop(drawable);
+        }
+        return drawable;
+    }
+
+}
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
new file mode 100644
index 0000000..0d1f301
--- /dev/null
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
@@ -0,0 +1,76 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+class DrawableWrapperLollipop extends DrawableWrapperKitKat {
+
+    DrawableWrapperLollipop(Drawable drawable) {
+        super(drawable);
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        mDrawable.setHotspot(x, y);
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        mDrawable.setHotspotBounds(left, top, right, bottom);
+    }
+
+    @Override
+    public void setTint(int tint) {
+        mDrawable.setTint(tint);
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mDrawable.setTintList(tint);
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mDrawable.setTintMode(tintMode);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        mDrawable.getOutline(outline);
+    }
+
+    @Override
+    public void applyTheme(Resources.Theme t) {
+        mDrawable.applyTheme(t);
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mDrawable.canApplyTheme();
+    }
+
+    @Override
+    public Rect getDirtyBounds() {
+        return mDrawable.getDirtyBounds();
+    }
+}
diff --git a/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
new file mode 100644
index 0000000..991515a
--- /dev/null
+++ b/v4/api21/android/support/v4/media/MediaDescriptionCompatApi21.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.media.MediaDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+
+public class MediaDescriptionCompatApi21 {
+
+    public static String getMediaId(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getMediaId();
+    }
+
+    public static CharSequence getTitle(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getTitle();
+    }
+
+    public static CharSequence getSubtitle(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getSubtitle();
+    }
+
+    public static CharSequence getDescription(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getDescription();
+    }
+
+    public static Bitmap getIconBitmap(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getIconBitmap();
+    }
+
+    public static Uri getIconUri(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getIconUri();
+    }
+
+    public static Bundle getExtras(Object descriptionObj) {
+        return ((MediaDescription) descriptionObj).getExtras();
+    }
+
+    public static void writeToParcel(Object descriptionObj, Parcel dest, int flags) {
+        ((MediaDescription) descriptionObj).writeToParcel(dest, flags);
+    }
+
+    public static Object fromParcel(Parcel in) {
+        return MediaDescription.CREATOR.createFromParcel(in);
+    }
+
+    public static class Builder {
+        public static Object newInstance() {
+            return new MediaDescription.Builder();
+        }
+
+
+        public static void setMediaId(Object builderObj, String mediaId) {
+            ((MediaDescription.Builder)builderObj).setMediaId(mediaId);
+        }
+
+        public static void setTitle(Object builderObj, CharSequence title) {
+            ((MediaDescription.Builder)builderObj).setTitle(title);
+        }
+
+        public static void setSubtitle(Object builderObj, CharSequence subtitle) {
+            ((MediaDescription.Builder)builderObj).setSubtitle(subtitle);
+        }
+
+        public static void setDescription(Object builderObj, CharSequence description) {
+            ((MediaDescription.Builder)builderObj).setDescription(description);
+        }
+
+        public static void setIconBitmap(Object builderObj, Bitmap iconBitmap) {
+            ((MediaDescription.Builder)builderObj).setIconBitmap(iconBitmap);
+        }
+
+        public static void setIconUri(Object builderObj, Uri iconUri) {
+            ((MediaDescription.Builder)builderObj).setIconUri(iconUri);
+        }
+
+        public static void setExtras(Object builderObj, Bundle extras) {
+            ((MediaDescription.Builder)builderObj).setExtras(extras);
+        }
+
+        public static Object build(Object builderObj) {
+            return ((MediaDescription.Builder) builderObj).build();
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
index cf7dde5..6acf425 100644
--- a/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.media.session;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
@@ -29,6 +30,9 @@
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 
+import java.util.ArrayList;
+import java.util.List;
+
 class MediaControllerCompatApi21 {
     public static Object fromToken(Context context, Object sessionToken) {
         return new MediaController(context, (MediaSession.Token) sessionToken);
@@ -60,21 +64,58 @@
         return ((MediaController)controllerObj).getMetadata();
     }
 
+    public static List<Object> getQueue(Object controllerObj) {
+        List<MediaSession.QueueItem> queue = ((MediaController) controllerObj).getQueue();
+        if (queue == null) {
+            return null;
+        }
+        List<Object> queueObjs = new ArrayList<Object>(queue);
+        return queueObjs;
+    }
+
+    public static CharSequence getQueueTitle(Object controllerObj) {
+        return ((MediaController) controllerObj).getQueueTitle();
+    }
+
+    public static Bundle getExtras(Object controllerObj) {
+        return ((MediaController) controllerObj).getExtras();
+    }
+
     public static int getRatingType(Object controllerObj) {
-        return ((MediaController)controllerObj).getRatingType();
+        return ((MediaController) controllerObj).getRatingType();
+    }
+
+    public static long getFlags(Object controllerObj) {
+        return ((MediaController) controllerObj).getFlags();
     }
 
     public static Object getPlaybackInfo(Object controllerObj) {
-        return ((MediaController)controllerObj).getPlaybackInfo();
+        return ((MediaController) controllerObj).getPlaybackInfo();
+    }
+
+    public static PendingIntent getSessionActivity(Object controllerObj) {
+        return ((MediaController) controllerObj).getSessionActivity();
     }
 
     public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
-        return ((MediaController)controllerObj).dispatchMediaButtonEvent(event);
+        return ((MediaController) controllerObj).dispatchMediaButtonEvent(event);
+    }
+
+    public static void setVolumeTo(Object controllerObj, int value, int flags) {
+        ((MediaController) controllerObj).setVolumeTo(value, flags);
+    }
+
+    public static void adjustVolume(Object controllerObj, int direction, int flags) {
+        ((MediaController) controllerObj).adjustVolume(direction, flags);
     }
 
     public static void sendCommand(Object controllerObj,
             String command, Bundle params, ResultReceiver cb) {
-        ((MediaController)controllerObj).sendCommand(command, params, cb);
+        ((MediaController) controllerObj).sendCommand(command, params, cb);
+    }
+
+    public static String getPackageName(Object controllerObj) {
+        return ((MediaController) controllerObj).getPackageName();
     }
 
     public static class TransportControls {
@@ -113,6 +154,22 @@
         public static void setRating(Object controlsObj, Object ratingObj) {
             ((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
         }
+
+        public static void playFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).playFromMediaId(mediaId, extras);
+        }
+
+        public static void playFromSearch(Object controlsObj, String query, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).playFromSearch(query, extras);
+        }
+
+        public static void skipToQueueItem(Object controlsObj, long id) {
+            ((MediaController.TransportControls) controlsObj).skipToQueueItem(id);
+        }
+
+        public static void sendCustomAction(Object controlsObj, String action, Bundle args) {
+            ((MediaController.TransportControls) controlsObj).sendCustomAction(action, args);
+        }
     }
 
     public static class PlaybackInfo {
diff --git a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
index 4977cba..9b07ea0 100644
--- a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -16,9 +16,11 @@
 
 package android.support.v4.media.session;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioAttributes;
+import android.media.MediaDescription;
 import android.media.MediaMetadata;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -29,6 +31,9 @@
 import android.os.Parcelable;
 import android.os.ResultReceiver;
 
+import java.util.ArrayList;
+import java.util.List;
+
 class MediaSessionCompatApi21 {
     public static Object createSession(Context context, String tag) {
         return new MediaSession(context, tag);
@@ -41,6 +46,13 @@
         throw new IllegalArgumentException("mediaSession is not a valid MediaSession object");
     }
 
+    public static Object verifyToken(Object token) {
+        if (token instanceof MediaSession.Token) {
+            return token;
+        }
+        throw new IllegalArgumentException("token is not a valid MediaSession.Token object");
+    }
+
     public static Object createCallback(Callback callback) {
         return new CallbackProxy<Callback>(callback);
     }
@@ -92,10 +104,41 @@
         ((MediaSession)sessionObj).setMetadata((MediaMetadata)metadataObj);
     }
 
+    public static void setSessionActivity(Object sessionObj, PendingIntent pi) {
+        ((MediaSession) sessionObj).setSessionActivity(pi);
+    }
+
+    public static void setMediaButtonReceiver(Object sessionObj, PendingIntent pi) {
+        ((MediaSession) sessionObj).setMediaButtonReceiver(pi);
+    }
+
+    public static void setQueue(Object sessionObj, List<Object> queueObjs) {
+        if (queueObjs == null) {
+            ((MediaSession) sessionObj).setQueue(null);
+            return;
+        }
+        ArrayList<MediaSession.QueueItem> queue = new ArrayList<MediaSession.QueueItem>();
+        for (Object itemObj : queueObjs) {
+            queue.add((MediaSession.QueueItem) itemObj);
+        }
+        ((MediaSession) sessionObj).setQueue(queue);
+    }
+
+    public static void setQueueTitle(Object sessionObj, CharSequence title) {
+        ((MediaSession) sessionObj).setQueueTitle(title);
+    }
+
+    public static void setExtras(Object sessionObj, Bundle extras) {
+        ((MediaSession) sessionObj).setExtras(extras);
+    }
+
     public static interface Callback {
         public void onCommand(String command, Bundle extras, ResultReceiver cb);
         public boolean onMediaButtonEvent(Intent mediaButtonIntent);
         public void onPlay();
+        public void onPlayFromMediaId(String mediaId, Bundle extras);
+        public void onPlayFromSearch(String search, Bundle extras);
+        public void onSkipToQueueItem(long id);
         public void onPause();
         public void onSkipToNext();
         public void onSkipToPrevious();
@@ -104,6 +147,7 @@
         public void onStop();
         public void onSeekTo(long pos);
         public void onSetRating(Object ratingObj);
+        public void onCustomAction(String action, Bundle extras);
     }
 
     static class CallbackProxy<T extends Callback> extends MediaSession.Callback {
@@ -168,4 +212,19 @@
             mCallback.onSetRating(rating);
         }
     }
+
+    static class QueueItem {
+
+        public static Object createItem(Object mediaDescription, long id) {
+            return new MediaSession.QueueItem((MediaDescription) mediaDescription, id);
+        }
+
+        public static Object getDescription(Object queueItem) {
+            return ((MediaSession.QueueItem) queueItem).getDescription();
+        }
+
+        public static long getQueueId(Object queueItem) {
+            return ((MediaSession.QueueItem) queueItem).getQueueId();
+        }
+    }
 }
diff --git a/v4/api21/android/support/v4/view/ViewCompatApi21.java b/v4/api21/android/support/v4/view/ViewCompatApi21.java
deleted file mode 100644
index ad343b7..0000000
--- a/v4/api21/android/support/v4/view/ViewCompatApi21.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.view;
-
-import android.view.View;
-import android.view.WindowInsets;
-
-class ViewCompatApi21 {
-
-    public static void setTransitionName(View view, String transitionName) {
-        view.setTransitionName(transitionName);
-    }
-
-    public static String getTransitionName(View view) {
-        return view.getTransitionName();
-    }
-
-    public static void requestApplyInsets(View view) {
-        view.requestApplyInsets();
-    }
-
-    public static void setElevation(View view, float elevation) {
-        view.setElevation(elevation);
-    }
-
-    public static float getElevation(View view) {
-        return view.getElevation();
-    }
-
-    public static void setTranslationZ(View view, float translationZ) {
-        view.setTranslationZ(translationZ);
-    }
-
-    public static float getTranslationZ(View view) {
-        return view.getTranslationZ();
-    }
-
-    public static void setOnApplyWindowInsetsListener(View view,
-            final OnApplyWindowInsetsListener listener) {
-        view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
-            @Override
-            public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
-                // Wrap the framework insets in our wrapper
-                WindowInsetsCompatApi21 insets = new WindowInsetsCompatApi21(windowInsets);
-                // Give the listener a chance to use the wrapped insets
-                insets = (WindowInsetsCompatApi21) listener.onApplyWindowInsets(view, insets);
-                // Return the unwrapped insets
-                return insets.unwrap();
-            }
-        });
-    }
-}
diff --git a/v4/api21/android/support/v4/view/ViewCompatLollipop.java b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
new file mode 100644
index 0000000..a9b4bdc
--- /dev/null
+++ b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.view.View;
+import android.view.WindowInsets;
+
+class ViewCompatLollipop {
+
+    public static void setTransitionName(View view, String transitionName) {
+        view.setTransitionName(transitionName);
+    }
+
+    public static String getTransitionName(View view) {
+        return view.getTransitionName();
+    }
+
+    public static void requestApplyInsets(View view) {
+        view.requestApplyInsets();
+    }
+
+    public static void setElevation(View view, float elevation) {
+        view.setElevation(elevation);
+    }
+
+    public static float getElevation(View view) {
+        return view.getElevation();
+    }
+
+    public static void setTranslationZ(View view, float translationZ) {
+        view.setTranslationZ(translationZ);
+    }
+
+    public static float getTranslationZ(View view) {
+        return view.getTranslationZ();
+    }
+
+    public static void setOnApplyWindowInsetsListener(View view,
+            final OnApplyWindowInsetsListener listener) {
+        view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+                // Wrap the framework insets in our wrapper
+                WindowInsetsCompatApi21 insets = new WindowInsetsCompatApi21(windowInsets);
+                // Give the listener a chance to use the wrapped insets
+                insets = (WindowInsetsCompatApi21) listener.onApplyWindowInsets(view, insets);
+                // Return the unwrapped insets
+                return insets.unwrap();
+            }
+        });
+    }
+
+    public static boolean isImportantForAccessibility(View view) {
+        return view.isImportantForAccessibility();
+    }
+
+    static ColorStateList getBackgroundTintList(View view) {
+        return view.getBackgroundTintList();
+    }
+
+    static void setBackgroundTintList(View view, ColorStateList tintList) {
+        view.setBackgroundTintList(tintList);
+    }
+
+    static PorterDuff.Mode getBackgroundTintMode(View view) {
+        return view.getBackgroundTintMode();
+    }
+
+    static void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
+        view.setBackgroundTintMode(mode);
+    }
+
+    public static WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
+        if (insets instanceof WindowInsetsCompatApi21) {
+            // First unwrap the compat version so that we have the framework instance
+            WindowInsets unwrapped = ((WindowInsetsCompatApi21) insets).unwrap();
+            // Now call onApplyWindowInsets
+            WindowInsets result = v.onApplyWindowInsets(unwrapped);
+
+            if (result != unwrapped) {
+                // ...and return a newly wrapped compat insets instance if different
+                insets = new WindowInsetsCompatApi21(result);
+            }
+        }
+        return insets;
+    }
+
+    public static WindowInsetsCompat dispatchApplyWindowInsets(View v, WindowInsetsCompat insets) {
+        if (insets instanceof WindowInsetsCompatApi21) {
+            // First unwrap the compat version so that we have the framework instance
+            WindowInsets unwrapped = ((WindowInsetsCompatApi21) insets).unwrap();
+            // Now call dispatchApplyWindowInsets
+            WindowInsets result = v.dispatchApplyWindowInsets(unwrapped);
+
+            if (result != unwrapped) {
+                // ...and return a newly wrapped compat insets instance if different
+                insets = new WindowInsetsCompatApi21(result);
+            }
+        }
+        return insets;
+    }
+
+    public static void setNestedScrollingEnabled(View view, boolean enabled) {
+        view.setNestedScrollingEnabled(enabled);
+    }
+
+    public static boolean isNestedScrollingEnabled(View view) {
+        return view.isNestedScrollingEnabled();
+    }
+
+    public static boolean startNestedScroll(View view, int axes) {
+        return view.startNestedScroll(axes);
+    }
+
+    public static void stopNestedScroll(View view) {
+        view.stopNestedScroll();
+    }
+
+    public static boolean hasNestedScrollingParent(View view) {
+        return view.hasNestedScrollingParent();
+    }
+
+    public static boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+        return view.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                offsetInWindow);
+    }
+
+    public static boolean dispatchNestedPreScroll(View view, int dx, int dy, int[] consumed,
+            int[] offsetInWindow) {
+        return view.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
+    }
+
+    public static boolean dispatchNestedFling(View view, float velocityX, float velocityY,
+            boolean consumed) {
+        return view.dispatchNestedFling(velocityX, velocityY, consumed);
+    }
+
+    public static boolean dispatchNestedPreFling(View view, float velocityX, float velocityY) {
+        return view.dispatchNestedPreFling(velocityX, velocityY);
+    }
+}
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v4/api21/android/support/v4/view/ViewGroupCompatLollipop.java
similarity index 86%
rename from v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
rename to v4/api21/android/support/v4/view/ViewGroupCompatLollipop.java
index 5ebd187..1a62404 100644
--- a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
+++ b/v4/api21/android/support/v4/view/ViewGroupCompatLollipop.java
@@ -18,7 +18,7 @@
 
 import android.view.ViewGroup;
 
-class ViewGroupCompatApi21 {
+class ViewGroupCompatLollipop {
 
     public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
         group.setTransitionGroup(isTransitionGroup);
@@ -27,4 +27,8 @@
     public static boolean isTransitionGroup(ViewGroup group) {
         return group.isTransitionGroup();
     }
+
+    public static int getNestedScrollAxes(ViewGroup group) {
+        return group.getNestedScrollAxes();
+    }
 }
diff --git a/v4/api21/android/support/v4/view/ViewParentCompatLollipop.java b/v4/api21/android/support/v4/view/ViewParentCompatLollipop.java
new file mode 100644
index 0000000..7dbcf61
--- /dev/null
+++ b/v4/api21/android/support/v4/view/ViewParentCompatLollipop.java
@@ -0,0 +1,98 @@
+/*
+ * 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.v4.view;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+
+class ViewParentCompatLollipop {
+    private static final String TAG = "ViewParentCompat";
+
+    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
+            int nestedScrollAxes) {
+        try {
+            return parent.onStartNestedScroll(child, target, nestedScrollAxes);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onStartNestedScroll", e);
+            return false;
+        }
+    }
+
+    public static void onNestedScrollAccepted(ViewParent parent, View child, View target,
+            int nestedScrollAxes) {
+        try {
+            parent.onNestedScrollAccepted(child, target, nestedScrollAxes);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onNestedScrollAccepted", e);
+        }
+    }
+
+    public static void onStopNestedScroll(ViewParent parent, View target) {
+        try {
+            parent.onStopNestedScroll(target);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onStopNestedScroll", e);
+        }
+    }
+
+    public static void onNestedScroll(ViewParent parent, View target, int dxConsumed,
+            int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+        try {
+            parent.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onNestedScroll", e);
+        }
+    }
+
+    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
+            int[] consumed) {
+        try {
+            parent.onNestedPreScroll(target, dx, dy, consumed);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onNestedPreScroll", e);
+        }
+    }
+
+    public static boolean onNestedFling(ViewParent parent, View target, float velocityX,
+            float velocityY, boolean consumed) {
+        try {
+            return parent.onNestedFling(target, velocityX, velocityY, consumed);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onNestedFling", e);
+            return false;
+        }
+    }
+
+    public static boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
+            float velocityY) {
+        try {
+            return parent.onNestedPreFling(target, velocityX, velocityY);
+        } catch (AbstractMethodError e) {
+            Log.e(TAG, "ViewParent " + parent + " does not implement interface " +
+                    "method onNestedPreFling", e);
+            return false;
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
index df4d299..0ae3a5c 100644
--- a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
+++ b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
@@ -30,10 +30,8 @@
         return (List<Object>) result;
     }
 
-    static void addAction(Object info, int id, CharSequence label) {
-        AccessibilityNodeInfo.AccessibilityAction aa =
-                new AccessibilityNodeInfo.AccessibilityAction(id, label);
-        ((AccessibilityNodeInfo) info).addAction(aa);
+    static void addAction(Object info, Object action) {
+        ((AccessibilityNodeInfo) info).addAction((AccessibilityAction) action);
     }
 
     public static Object obtainCollectionInfo(int rowCount, int columnCount,
@@ -54,13 +52,15 @@
         }
     }
 
-    static class AccessibilityAction {
-        static int getId(Object action) {
-            return ((AccessibilityNodeInfo.AccessibilityAction) action).getId();
-        }
+    static Object newAccessibilityAction(int actionId, CharSequence label) {
+        return new AccessibilityAction(actionId, label);
+    }
 
-        static CharSequence getLabel(Object action) {
-            return ((AccessibilityNodeInfo.AccessibilityAction) action).getLabel();
-        }
+    static int getAccessibilityActionId(Object action) {
+        return ((AccessibilityNodeInfo.AccessibilityAction) action).getId();
+    }
+
+    static CharSequence getAccessibilityActionLabel(Object action) {
+        return ((AccessibilityNodeInfo.AccessibilityAction) action).getLabel();
     }
 }
diff --git a/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java b/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
index 12e9555..07cc3fa 100644
--- a/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
+++ b/v4/api21/android/support/v4/widget/DrawerLayoutCompatApi21.java
@@ -17,6 +17,9 @@
 
 package android.support.v4.widget;
 
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -26,6 +29,11 @@
  * Provides functionality for DrawerLayout unique to API 21
  */
 class DrawerLayoutCompatApi21 {
+
+    private static final int[] THEME_ATTRS = {
+            android.R.attr.colorPrimaryDark
+    };
+
     public static void configureApplyInsets(View drawerLayout) {
         if (drawerLayout instanceof DrawerLayoutImpl) {
             drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener());
@@ -66,6 +74,15 @@
         return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0;
     }
 
+    public static Drawable getDefaultStatusBarBackground(Context context) {
+        final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
+        try {
+            return a.getDrawable(0);
+        } finally {
+            a.recycle();
+        }
+    }
+
     static class InsetsListener implements View.OnApplyWindowInsetsListener {
         @Override
         public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
diff --git a/v4/api21/android/support/v4/widget/EdgeEffectCompatLollipop.java b/v4/api21/android/support/v4/widget/EdgeEffectCompatLollipop.java
new file mode 100644
index 0000000..6ba379c
--- /dev/null
+++ b/v4/api21/android/support/v4/widget/EdgeEffectCompatLollipop.java
@@ -0,0 +1,27 @@
+/*
+ * 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.v4.widget;
+
+import android.widget.EdgeEffect;
+
+class EdgeEffectCompatLollipop {
+    public static boolean onPull(Object edgeEffect, float deltaDistance, float displacement) {
+        ((EdgeEffect) edgeEffect).onPull(deltaDistance, displacement);
+        return true;
+    }
+}
diff --git a/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java b/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
new file mode 100644
index 0000000..bfd2bea
--- /dev/null
+++ b/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Implementation of drawable compatibility that can call Lollipop-MR1 APIs.
+ */
+class DrawableCompatApi22 {
+
+    public static Drawable wrapForTinting(Drawable drawable) {
+        // We don't need to wrap anything in Lollipop-MR1
+        return drawable;
+    }
+
+}
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
similarity index 63%
copy from v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
copy to v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
index 5ebd187..b847778 100644
--- a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
+++ b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
@@ -13,18 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.support.v4.media.session;
 
-package android.support.v4.view;
+import android.media.session.MediaSession;
 
-import android.view.ViewGroup;
+class MediaSessionCompatApi22 {
 
-class ViewGroupCompatApi21 {
-
-    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-        group.setTransitionGroup(isTransitionGroup);
-    }
-
-    public static boolean isTransitionGroup(ViewGroup group) {
-        return group.isTransitionGroup();
+    public static void setRatingType(Object sessionObj, int type) {
+        ((MediaSession) sessionObj).setRatingType(type);
     }
 }
diff --git a/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java b/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java
new file mode 100644
index 0000000..786318d
--- /dev/null
+++ b/v4/api22/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.View;
+
+/**
+ * Api22-specific AccessibilityNodeInfo API implementation.
+ */
+class AccessibilityNodeInfoCompatApi22 {
+
+    public static Object getTraversalBefore(Object info) {
+        return ((AccessibilityNodeInfo) info).getTraversalBefore();
+    }
+
+    public static void setTraversalBefore(Object info, View view) {
+        ((AccessibilityNodeInfo) info).setTraversalBefore(view);
+    }
+
+    public static void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+        ((AccessibilityNodeInfo) info).setTraversalBefore(root, virtualDescendantId);
+    }
+
+    public static Object getTraversalAfter(Object info) {
+        return ((AccessibilityNodeInfo) info).getTraversalAfter();
+    }
+
+    public static void setTraversalAfter(Object info, View view) {
+        ((AccessibilityNodeInfo) info).setTraversalAfter(view);
+    }
+
+    public static void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+        ((AccessibilityNodeInfo) info).setTraversalAfter(root, virtualDescendantId);
+    }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index fed5c24..d689a3f 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -27,8 +27,9 @@
 def jbMr1SS        = createApiSourceset('jellybeanmr1', 'jellybean-mr1', '17',      jbSS)
 def jbMr2SS        = createApiSourceset('jellybeanmr2', 'jellybean-mr2', '18',      jbMr1SS)
 def kitkatSS       = createApiSourceset('kitkat',       'kitkat',        '19',      jbMr2SS)
-def api20SS        = createApiSourceset('api20',        'api20',         'current', kitkatSS)
-def api21SS        = createApiSourceset('api21',        'api21',         'current', api20SS)
+def api20SS        = createApiSourceset('api20',        'api20',         '20', kitkatSS)
+def api21SS        = createApiSourceset('api21',        'api21',         '21', api20SS)
+def api22SS        = createApiSourceset('api22',        'api22',         'current', api21SS)
 
 
 def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
@@ -55,6 +56,7 @@
 
 dependencies {
     compile project(':support-annotations')
+    donutCompile project(':support-annotations')
 
     // add the internal implementation as a dependency.
     // this is not enough to make the regular compileJava task
@@ -86,6 +88,11 @@
         // TODO: fix errors and reenable.
         abortOnError false
     }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
 }
 
 android.libraryVariants.all { variant ->
diff --git a/v4/donut/android/support/v4/app/NotificationCompatBase.java b/v4/donut/android/support/v4/app/NotificationCompatBase.java
index d8e99af..fdc1da0 100644
--- a/v4/donut/android/support/v4/app/NotificationCompatBase.java
+++ b/v4/donut/android/support/v4/app/NotificationCompatBase.java
@@ -34,4 +34,21 @@
             public Action[] newArray(int length);
         }
     }
+
+    public static abstract class UnreadConversation {
+        abstract String[] getParticipants();
+        abstract String getParticipant();
+        abstract String[] getMessages();
+        abstract RemoteInputCompatBase.RemoteInput getRemoteInput();
+        abstract PendingIntent getReplyPendingIntent();
+        abstract PendingIntent getReadPendingIntent();
+        abstract long getLatestTimestamp();
+
+        public interface Factory {
+            UnreadConversation build(String[] messages,
+                    RemoteInputCompatBase.RemoteInput remoteInput,
+                    PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+                    String[] participants, long latestTimestamp);
+        }
+    }
 }
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java b/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java
new file mode 100644
index 0000000..4809618
--- /dev/null
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java
@@ -0,0 +1,53 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Base implementation of drawable compatibility.
+ */
+class DrawableCompatBase {
+
+    public static void setTint(Drawable drawable, int tint) {
+        if (drawable instanceof DrawableWrapper) {
+            ((DrawableWrapper) drawable).setTint(tint);
+        }
+    }
+
+    public static void setTintList(Drawable drawable, ColorStateList tint) {
+        if (drawable instanceof DrawableWrapper) {
+            ((DrawableWrapper) drawable).setTintList(tint);
+        }
+    }
+
+    public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
+        if (drawable instanceof DrawableWrapper) {
+            ((DrawableWrapper) drawable).setTintMode(tintMode);
+        }
+    }
+
+    public static Drawable wrapForTinting(Drawable drawable) {
+        if (!(drawable instanceof DrawableWrapperDonut)) {
+            return new DrawableWrapperDonut(drawable);
+        }
+        return drawable;
+    }
+
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java
new file mode 100644
index 0000000..1073f34
--- /dev/null
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Interface which allows a {@link android.graphics.drawable.Drawable} to receive tinting calls from
+ * {@code DrawableCompat}.
+ *
+ * @hide
+ */
+public interface DrawableWrapper {
+
+    void setTint(int tint);
+
+    void setTintList(ColorStateList tint);
+
+    void setTintMode(PorterDuff.Mode tintMode);
+
+    Drawable getWrappedDrawable();
+
+    void setWrappedDrawable(Drawable drawable);
+
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
new file mode 100644
index 0000000..ce8e777
--- /dev/null
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
@@ -0,0 +1,240 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Drawable which delegates all calls to it's wrapped {@link android.graphics.drawable.Drawable}.
+ * <p>
+ * Also allows backward compatible tinting via a color or {@link ColorStateList}.
+ * This functionality is accessed via static methods in {@code DrawableCompat}.
+ */
+class DrawableWrapperDonut extends Drawable implements Drawable.Callback, DrawableWrapper {
+
+    static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
+
+    private ColorStateList mTintList;
+    private PorterDuff.Mode mTintMode = DEFAULT_MODE;
+
+    private int mCurrentColor = Integer.MIN_VALUE;
+
+    Drawable mDrawable;
+
+    DrawableWrapperDonut(Drawable drawable) {
+        setWrappedDrawable(drawable);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mDrawable.draw(canvas);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        mDrawable.setBounds(bounds);
+    }
+
+    @Override
+    public void setChangingConfigurations(int configs) {
+        mDrawable.setChangingConfigurations(configs);
+    }
+
+    @Override
+    public int getChangingConfigurations() {
+        return mDrawable.getChangingConfigurations();
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        mDrawable.setDither(dither);
+    }
+
+    @Override
+    public void setFilterBitmap(boolean filter) {
+        mDrawable.setFilterBitmap(filter);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mDrawable.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mDrawable.setColorFilter(cf);
+    }
+
+    @Override
+    public boolean isStateful() {
+        return (mTintList != null && mTintList.isStateful()) || mDrawable.isStateful();
+    }
+
+    @Override
+    public boolean setState(final int[] stateSet) {
+        boolean handled = mDrawable.setState(stateSet);
+        handled = updateTint(stateSet) || handled;
+        return handled;
+    }
+
+    @Override
+    public int[] getState() {
+        return mDrawable.getState();
+    }
+
+    @Override
+    public Drawable getCurrent() {
+        return mDrawable.getCurrent();
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart);
+    }
+
+    @Override
+    public int getOpacity() {
+        return mDrawable.getOpacity();
+    }
+
+    @Override
+    public Region getTransparentRegion() {
+        return mDrawable.getTransparentRegion();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public int getMinimumWidth() {
+        return mDrawable.getMinimumWidth();
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        return mDrawable.getMinimumHeight();
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        return mDrawable.getPadding(padding);
+    }
+
+    @Override
+    public Drawable mutate() {
+        Drawable wrapped = mDrawable;
+        Drawable mutated = wrapped.mutate();
+        if (mutated != wrapped) {
+            // If mutate() returned a new instance, update our reference
+            setWrappedDrawable(mutated);
+        }
+        // We return ourselves, since only the wrapped drawable needs to mutate
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void invalidateDrawable(Drawable who) {
+        invalidateSelf();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void scheduleDrawable(Drawable who, Runnable what, long when) {
+        scheduleSelf(what, when);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void unscheduleDrawable(Drawable who, Runnable what) {
+        unscheduleSelf(what);
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        return mDrawable.setLevel(level);
+    }
+
+    @Override
+    public void setTint(int tint) {
+        setTintList(ColorStateList.valueOf(tint));
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        mTintList = tint;
+        updateTint(getState());
+    }
+
+    @Override
+    public void setTintMode(PorterDuff.Mode tintMode) {
+        mTintMode = tintMode;
+        updateTint(getState());
+    }
+
+    private boolean updateTint(int[] state) {
+        if (mTintList != null && mTintMode != null) {
+            final int color = mTintList.getColorForState(state, mTintList.getDefaultColor());
+            if (color != mCurrentColor) {
+                setColorFilter(color, mTintMode);
+                mCurrentColor = color;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the wrapped {@link Drawable}
+     */
+    public Drawable getWrappedDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets the current wrapped {@link Drawable}
+     */
+    public void setWrappedDrawable(Drawable drawable) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+        }
+
+        mDrawable = drawable;
+
+        if (drawable != null) {
+            drawable.setCallback(this);
+        }
+        // Invalidate ourselves
+        invalidateSelf();
+    }
+}
diff --git a/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java b/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java
new file mode 100644
index 0000000..8f210d3
--- /dev/null
+++ b/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+class LayoutInflaterCompatBase {
+
+    static class FactoryWrapper implements LayoutInflater.Factory {
+
+        final LayoutInflaterFactory mDelegateFactory;
+
+        FactoryWrapper(LayoutInflaterFactory delegateFactory) {
+            mDelegateFactory = delegateFactory;
+        }
+
+        @Override
+        public View onCreateView(String name, Context context, AttributeSet attrs) {
+            return mDelegateFactory.onCreateView(null, name, context, attrs);
+        }
+    }
+
+    static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
+        inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
+    }
+
+}
diff --git a/v4/donut/android/support/v4/view/LayoutInflaterFactory.java b/v4/donut/android/support/v4/view/LayoutInflaterFactory.java
new file mode 100644
index 0000000..02b3d63
--- /dev/null
+++ b/v4/donut/android/support/v4/view/LayoutInflaterFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Used with {@code LayoutInflaterCompat.setFactory()}. Offers the same API as
+ * {@code LayoutInflater.Factory2}.
+ */
+public interface LayoutInflaterFactory {
+
+    /**
+     * Hook you can supply that is called when inflating from a LayoutInflater.
+     * You can use this to customize the tag names available in your XML
+     * layout files.
+     *
+     * @param parent The parent that the created view will be placed
+     * in; <em>note that this may be null</em>.
+     * @param name Tag name to be inflated.
+     * @param context The context the view is being created in.
+     * @param attrs Inflation attributes as specified in XML file.
+     *
+     * @return View Newly created view. Return null for the default
+     *         behavior.
+     */
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
+
+}
diff --git a/v4/donut/android/support/v4/view/TintableBackgroundView.java b/v4/donut/android/support/v4/view/TintableBackgroundView.java
new file mode 100644
index 0000000..83c014b
--- /dev/null
+++ b/v4/donut/android/support/v4/view/TintableBackgroundView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.annotation.Nullable;
+
+/**
+ * Interface which allows a {@link android.view.View} to receive background tinting calls from
+ * {@code ViewCompat} when running on API v20 devices or lower.
+ */
+public interface TintableBackgroundView {
+
+    /**
+     * Applies a tint to the background drawable. Does not modify the current tint
+     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+     * <p>
+     * Subsequent calls to {@code View.setBackground(Drawable)} will automatically
+     * mutate the drawable and apply the specified tint and tint mode.
+     *
+     * @param tint the tint to apply, may be {@code null} to clear tint
+     *
+     * @see #getSupportBackgroundTintList()
+     */
+    void setSupportBackgroundTintList(@Nullable ColorStateList tint);
+
+    /**
+     * Return the tint applied to the background drawable, if specified.
+     *
+     * @return the tint applied to the background drawable
+     */
+    @Nullable
+    ColorStateList getSupportBackgroundTintList();
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setSupportBackgroundTintList(ColorStateList)}} to the background
+     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     *
+     * @param tintMode the blending mode used to apply the tint, may be
+     *                 {@code null} to clear tint
+     * @see #getSupportBackgroundTintMode()
+     */
+    void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode);
+
+    /**
+     * Return the blending mode used to apply the tint to the background
+     * drawable, if specified.
+     *
+     * @return the blending mode used to apply the tint to the background
+     *         drawable
+     */
+    @Nullable
+    PorterDuff.Mode getSupportBackgroundTintMode();
+}
diff --git a/v4/donut/android/support/v4/view/ViewCompatBase.java b/v4/donut/android/support/v4/view/ViewCompatBase.java
new file mode 100644
index 0000000..7f525aa
--- /dev/null
+++ b/v4/donut/android/support/v4/view/ViewCompatBase.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.view.View;
+
+class ViewCompatBase {
+
+    static ColorStateList getBackgroundTintList(View view) {
+        return (view instanceof TintableBackgroundView)
+                ? ((TintableBackgroundView) view).getSupportBackgroundTintList()
+                : null;
+    }
+
+    static void setBackgroundTintList(View view, ColorStateList tintList) {
+        if (view instanceof TintableBackgroundView) {
+            ((TintableBackgroundView) view).setSupportBackgroundTintList(tintList);
+        }
+    }
+
+    static PorterDuff.Mode getBackgroundTintMode(View view) {
+        return (view instanceof TintableBackgroundView)
+                ? ((TintableBackgroundView) view).getSupportBackgroundTintMode()
+                : null;
+    }
+
+    static void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
+        if (view instanceof TintableBackgroundView) {
+            ((TintableBackgroundView) view).setSupportBackgroundTintMode(mode);
+        }
+    }
+
+    static boolean isLaidOut(View view) {
+        return view.getWidth() > 0 && view.getHeight() > 0;
+    }
+}
diff --git a/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
new file mode 100644
index 0000000..f49eb2b
--- /dev/null
+++ b/v4/froyo/android/support/v4/media/session/MediaSessionCompatApi8.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+
+public class MediaSessionCompatApi8 {
+    public static void registerMediaButtonEventReceiver(Context context, ComponentName mbr) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.registerMediaButtonEventReceiver(mbr);
+    }
+
+    public static void unregisterMediaButtonEventReceiver(Context context, ComponentName mbr) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.unregisterMediaButtonEventReceiver(mbr);
+    }
+}
\ No newline at end of file
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v4/gingerbread/android/support/v4/view/MotionEventCompatGingerbread.java
similarity index 61%
copy from v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
copy to v4/gingerbread/android/support/v4/view/MotionEventCompatGingerbread.java
index 5ebd187..f43dc0a 100644
--- a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
+++ b/v4/gingerbread/android/support/v4/view/MotionEventCompatGingerbread.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -16,15 +16,13 @@
 
 package android.support.v4.view;
 
-import android.view.ViewGroup;
+import android.view.MotionEvent;
 
-class ViewGroupCompatApi21 {
-
-    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-        group.setTransitionGroup(isTransitionGroup);
-    }
-
-    public static boolean isTransitionGroup(ViewGroup group) {
-        return group.isTransitionGroup();
+/**
+ * Motion event compatibility class for API 8+.
+ */
+class MotionEventCompatGingerbread {
+    public static int getSource(MotionEvent event) {
+        return event.getSource();
     }
 }
diff --git a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
index 4c5d48b..def23ed 100644
--- a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
@@ -16,13 +16,23 @@
 
 package android.support.v4.graphics.drawable;
 
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 
 /**
  * Implementation of drawable compatibility that can call Honeycomb APIs.
  */
 class DrawableCompatHoneycomb {
+
     public static void jumpToCurrentState(Drawable drawable) {
         drawable.jumpToCurrentState();
     }
+
+    public static Drawable wrapForTinting(Drawable drawable) {
+        if (!(drawable instanceof DrawableWrapperHoneycomb)) {
+            return new DrawableWrapperHoneycomb(drawable);
+        }
+        return drawable;
+    }
 }
diff --git a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
new file mode 100644
index 0000000..f9fd7d8
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.graphics.drawable.Drawable;
+
+class DrawableWrapperHoneycomb extends DrawableWrapperDonut {
+
+    DrawableWrapperHoneycomb(Drawable drawable) {
+        super(drawable);
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        mDrawable.jumpToCurrentState();
+    }
+}
diff --git a/v4/honeycomb/android/support/v4/view/LayoutInflaterCompatHC.java b/v4/honeycomb/android/support/v4/view/LayoutInflaterCompatHC.java
new file mode 100644
index 0000000..b57731c
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/view/LayoutInflaterCompatHC.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+class LayoutInflaterCompatHC {
+
+    static class FactoryWrapperHC extends LayoutInflaterCompatBase.FactoryWrapper
+            implements LayoutInflater.Factory2 {
+
+        FactoryWrapperHC(LayoutInflaterFactory delegateFactory) {
+            super(delegateFactory);
+        }
+
+        @Override
+        public View onCreateView(View parent, String name, Context context,
+                AttributeSet attributeSet) {
+            return mDelegateFactory.onCreateView(parent, name, context, attributeSet);
+        }
+    }
+
+    static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
+        inflater.setFactory2(factory != null ? new FactoryWrapperHC(factory) : null);
+    }
+
+}
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index 1c35bd3..fbcc31e 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -148,4 +148,12 @@
     public static void jumpDrawablesToCurrentState(View view) {
         view.jumpDrawablesToCurrentState();
     }
+
+    public static void setSaveFromParentEnabled(View view, boolean enabled) {
+        view.setSaveFromParentEnabled(enabled);
+    }
+
+    public static void setActivated(View view, boolean activated) {
+        view.setActivated(activated);
+    }
 }
diff --git a/v4/honeycomb_mr1/android/support/v4/view/MotionEventCompatHoneycombMr1.java b/v4/honeycomb_mr1/android/support/v4/view/MotionEventCompatHoneycombMr1.java
new file mode 100644
index 0000000..406fcf3
--- /dev/null
+++ b/v4/honeycomb_mr1/android/support/v4/view/MotionEventCompatHoneycombMr1.java
@@ -0,0 +1,32 @@
+/*
+ * 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.v4.view;
+
+import android.view.MotionEvent;
+
+/**
+ * Motion event compatibility class for API 12+.
+ */
+class MotionEventCompatHoneycombMr1 {
+    static float getAxisValue(MotionEvent event, int axis) {
+        return event.getAxisValue(axis);
+    }
+
+    static float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
+        return event.getAxisValue(axis, pointerIndex);
+    }
+}
diff --git a/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java b/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java
new file mode 100644
index 0000000..8e14256
--- /dev/null
+++ b/v4/ics-mr1/android/support/v4/content/res/ResourcesCompatIcsMr1.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+
+class ResourcesCompatIcsMr1 {
+    public static Drawable getDrawableForDensity(Resources res, int id, int density)
+            throws NotFoundException {
+        return res.getDrawableForDensity(id, density);
+    }
+}
diff --git a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
new file mode 100644
index 0000000..ff4660e
--- /dev/null
+++ b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+public class MediaSessionCompatApi14 {
+    /***** RemoteControlClient States, we only need none as the others were public *******/
+    final static int RCC_PLAYSTATE_NONE = 0;
+
+    /***** MediaSession States *******/
+    final static int STATE_NONE = 0;
+    final static int STATE_STOPPED = 1;
+    final static int STATE_PAUSED = 2;
+    final static int STATE_PLAYING = 3;
+    final static int STATE_FAST_FORWARDING = 4;
+    final static int STATE_REWINDING = 5;
+    final static int STATE_BUFFERING = 6;
+    final static int STATE_ERROR = 7;
+    final static int STATE_CONNECTING = 8;
+    final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+    final static int STATE_SKIPPING_TO_NEXT = 10;
+
+    /***** MediaMetadata keys ********/
+    private static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+    private static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+    private static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+    private static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+    private static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+    private static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+    private static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+    private static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+    private static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+    private static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+    private static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+    private static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+    private static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+    private static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+    private static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+    public static Object createRemoteControlClient(PendingIntent mbIntent) {
+        return new RemoteControlClient(mbIntent);
+    }
+
+    public static void setState(Object rccObj, int state) {
+        ((RemoteControlClient) rccObj).setPlaybackState(getRccStateFromState(state));
+    }
+
+    public static void setMetadata(Object rccObj, Bundle metadata) {
+        RemoteControlClient.MetadataEditor editor = ((RemoteControlClient) rccObj).editMetadata(
+                true);
+        buildOldMetadata(metadata, editor);
+        editor.apply();
+    }
+
+    public static void registerRemoteControlClient(Context context, Object rccObj) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.registerRemoteControlClient((RemoteControlClient) rccObj);
+    }
+
+    public static void unregisterRemoteControlClient(Context context, Object rccObj) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.unregisterRemoteControlClient((RemoteControlClient) rccObj);
+    }
+
+    static int getRccStateFromState(int state) {
+        switch (state) {
+            case STATE_CONNECTING:
+            case STATE_BUFFERING:
+                return RemoteControlClient.PLAYSTATE_BUFFERING;
+            case STATE_ERROR:
+                return RemoteControlClient.PLAYSTATE_ERROR;
+            case STATE_FAST_FORWARDING:
+                return RemoteControlClient.PLAYSTATE_FAST_FORWARDING;
+            case STATE_NONE:
+                return RCC_PLAYSTATE_NONE;
+            case STATE_PAUSED:
+                return RemoteControlClient.PLAYSTATE_PAUSED;
+            case STATE_PLAYING:
+                return RemoteControlClient.PLAYSTATE_PLAYING;
+            case STATE_REWINDING:
+                return RemoteControlClient.PLAYSTATE_REWINDING;
+            case STATE_SKIPPING_TO_PREVIOUS:
+                return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS;
+            case STATE_SKIPPING_TO_NEXT:
+                return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS;
+            case STATE_STOPPED:
+                return RemoteControlClient.PLAYSTATE_STOPPED;
+            default:
+                return -1;
+        }
+    }
+
+    static void buildOldMetadata(Bundle metadata, RemoteControlClient.MetadataEditor editor) {
+        if (metadata.containsKey(METADATA_KEY_ALBUM)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM,
+                    metadata.getString(METADATA_KEY_ALBUM));
+        }
+        if (metadata.containsKey(METADATA_KEY_ALBUM_ARTIST)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST,
+                    metadata.getString(METADATA_KEY_ALBUM_ARTIST));
+        }
+        if (metadata.containsKey(METADATA_KEY_ARTIST)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST,
+                    metadata.getString(METADATA_KEY_ARTIST));
+        }
+        if (metadata.containsKey(METADATA_KEY_AUTHOR)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_AUTHOR,
+                    metadata.getString(METADATA_KEY_AUTHOR));
+        }
+        if (metadata.containsKey(METADATA_KEY_COMPILATION)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPILATION,
+                    metadata.getString(METADATA_KEY_COMPILATION));
+        }
+        if (metadata.containsKey(METADATA_KEY_COMPOSER)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_COMPOSER,
+                    metadata.getString(METADATA_KEY_COMPOSER));
+        }
+        if (metadata.containsKey(METADATA_KEY_DATE)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_DATE,
+                    metadata.getString(METADATA_KEY_DATE));
+        }
+        if (metadata.containsKey(METADATA_KEY_DISC_NUMBER)) {
+            editor.putLong(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER,
+                    metadata.getLong(METADATA_KEY_DISC_NUMBER));
+        }
+        if (metadata.containsKey(METADATA_KEY_DURATION)) {
+            editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
+                    metadata.getLong(METADATA_KEY_DURATION));
+        }
+        if (metadata.containsKey(METADATA_KEY_GENRE)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_GENRE,
+                    metadata.getString(METADATA_KEY_GENRE));
+        }
+        if (metadata.containsKey(METADATA_KEY_NUM_TRACKS)) {
+            editor.putLong(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS,
+                    metadata.getLong(METADATA_KEY_NUM_TRACKS));
+        }
+        if (metadata.containsKey(METADATA_KEY_TITLE)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE,
+                    metadata.getString(METADATA_KEY_TITLE));
+        }
+        if (metadata.containsKey(METADATA_KEY_TRACK_NUMBER)) {
+            editor.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER,
+                    metadata.getLong(METADATA_KEY_TRACK_NUMBER));
+        }
+        if (metadata.containsKey(METADATA_KEY_WRITER)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_WRITER,
+                    metadata.getString(METADATA_KEY_WRITER));
+        }
+        if (metadata.containsKey(METADATA_KEY_YEAR)) {
+            editor.putString(MediaMetadataRetriever.METADATA_KEY_YEAR,
+                    metadata.getString(METADATA_KEY_YEAR));
+        }
+    }
+
+    public static interface Callback {
+        public void onCommand(String command, Bundle extras, ResultReceiver cb);
+
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent);
+
+        public void onPlay();
+
+        public void onPause();
+
+        public void onSkipToNext();
+
+        public void onSkipToPrevious();
+
+        public void onFastForward();
+
+        public void onRewind();
+
+        public void onStop();
+
+        public void onSeekTo(long pos);
+
+        public void onSetRating(Object ratingObj);
+    }
+}
\ No newline at end of file
diff --git a/v4/ics/android/support/v4/view/ViewCompatICS.java b/v4/ics/android/support/v4/view/ViewCompatICS.java
index 82aeaf3..742c47c 100644
--- a/v4/ics/android/support/v4/view/ViewCompatICS.java
+++ b/v4/ics/android/support/v4/view/ViewCompatICS.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.view;
 
+import android.support.annotation.Nullable;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 import android.view.accessibility.AccessibilityEvent;
@@ -34,7 +35,7 @@
         return v.canScrollVertically(direction);
     }
 
-    public static void setAccessibilityDelegate(View v, Object delegate) {
+    public static void setAccessibilityDelegate(View v, @Nullable Object delegate) {
         v.setAccessibilityDelegate((AccessibilityDelegate) delegate);
     }
 
@@ -49,4 +50,8 @@
     public static void onInitializeAccessibilityNodeInfo(View v, Object info) {
         v.onInitializeAccessibilityNodeInfo((AccessibilityNodeInfo) info);
     }
+
+    public static void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+        view.setFitsSystemWindows(fitSystemWindows);
+    }
 }
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index ab23a6f..da9354e 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -1076,10 +1076,10 @@
     }
 
     private static Object captureExitingViews(Object exitTransition, Fragment outFragment,
-            ArrayList<View> exitingViews, ArrayMap<String, View> namedViews) {
+            ArrayList<View> exitingViews, ArrayMap<String, View> namedViews, View nonExistentView) {
         if (exitTransition != null) {
             exitTransition = FragmentTransitionCompat21.captureExitingViews(exitTransition,
-                    outFragment.getView(), exitingViews, namedViews);
+                    outFragment.getView(), exitingViews, namedViews, nonExistentView);
         }
         return exitTransition;
     }
@@ -1147,11 +1147,8 @@
         ArrayList<View> sharedElementTargets = new ArrayList<View>();
         if (sharedElementTransition != null) {
             namedViews = remapSharedElements(state, outFragment, isBack);
-            if (namedViews.isEmpty()) {
-                sharedElementTargets.add(state.nonExistentView);
-            } else {
-                sharedElementTargets.addAll(namedViews.values());
-            }
+            sharedElementTargets.add(state.nonExistentView);
+            sharedElementTargets.addAll(namedViews.values());
 
             // Notify the start of the transition.
             SharedElementCallback callback = isBack ?
@@ -1166,7 +1163,7 @@
 
         ArrayList<View> exitingViews = new ArrayList<View>();
         exitTransition = captureExitingViews(exitTransition, outFragment, exitingViews,
-                namedViews);
+                namedViews, state.nonExistentView);
 
         // Set the epicenter of the exit transition
         if (mSharedElementTargetNames != null && namedViews != null) {
@@ -1243,11 +1240,8 @@
 
                     ArrayMap<String, View> namedViews = mapSharedElementsIn(
                             state, isBack, inFragment);
-                    if (namedViews.isEmpty()) {
-                        sharedElementTargets.add(state.nonExistentView);
-                    } else {
-                        sharedElementTargets.addAll(namedViews.values());
-                    }
+                    sharedElementTargets.add(state.nonExistentView);
+                    sharedElementTargets.addAll(namedViews.values());
                     FragmentTransitionCompat21.addTargets(sharedElementTransition,
                             sharedElementTargets);
 
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 9bad061..7889a59 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -1741,6 +1741,7 @@
         mChildFragmentManager = new FragmentManagerImpl();
         mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
             @Override
+            @Nullable
             public View findViewById(int id) {
                 if (mView == null) {
                     throw new IllegalStateException("Fragment does not have a view");
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index bb1a001d..db53c96 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -26,6 +26,7 @@
 import android.os.Message;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.util.SimpleArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -105,6 +106,7 @@
     final FragmentManagerImpl mFragments = new FragmentManagerImpl();
     final FragmentContainer mContainer = new FragmentContainer() {
         @Override
+        @Nullable
         public View findViewById(int id) {
             return FragmentActivity.this.findViewById(id);
         }
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index 7116ee3..f15bb79 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -19,15 +19,18 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.IdRes;
+import android.support.annotation.Nullable;
 import android.support.annotation.StringRes;
 import android.support.v4.util.DebugUtils;
 import android.support.v4.util.LogWriter;
+import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -399,6 +402,7 @@
  * Callbacks from FragmentManagerImpl to its container.
  */
 interface FragmentContainer {
+    @Nullable
     public View findViewById(@IdRes int id);
     public boolean hasView();
 }
@@ -921,7 +925,11 @@
                                 f.mSavedFragmentState), null, f.mSavedFragmentState);
                         if (f.mView != null) {
                             f.mInnerView = f.mView;
-                            f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+                            if (Build.VERSION.SDK_INT >= 11) {
+                                ViewCompat.setSaveFromParentEnabled(f.mView, false);
+                            } else {
+                                f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+                            }
                             if (f.mHidden) f.mView.setVisibility(View.GONE);
                             f.onViewCreated(f.mView, f.mSavedFragmentState);
                         } else {
@@ -948,7 +956,11 @@
                                     f.mSavedFragmentState), container, f.mSavedFragmentState);
                             if (f.mView != null) {
                                 f.mInnerView = f.mView;
-                                f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+                                if (Build.VERSION.SDK_INT >= 11) {
+                                    ViewCompat.setSaveFromParentEnabled(f.mView, false);
+                                } else {
+                                    f.mView = NoSaveStateFrameLayout.wrap(f.mView);
+                                }
                                 if (container != null) {
                                     Animation anim = loadAnimation(f, transit, true,
                                             transitionStyle);
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index 4b355cc..e007bcd 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -453,6 +453,10 @@
         public String getGroup(Notification n);
         public boolean isGroupSummary(Notification n);
         public String getSortKey(Notification n);
+        Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc);
+        NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+                Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+                RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory);
     }
 
     static class NotificationCompatImplBase implements NotificationCompatImpl {
@@ -518,6 +522,18 @@
         public String getSortKey(Notification n) {
             return null;
         }
+
+        @Override
+        public Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+            return null;
+        }
+
+        @Override
+        public NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+                Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+                RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+            return null;
+        }
     }
 
     static class NotificationCompatImplGingerbread extends NotificationCompatImplBase {
@@ -743,6 +759,19 @@
         public String getCategory(Notification notif) {
             return NotificationCompatApi21.getCategory(notif);
         }
+
+        @Override
+        public Bundle getBundleForUnreadConversation(NotificationCompatBase.UnreadConversation uc) {
+            return NotificationCompatApi21.getBundleForUnreadConversation(uc);
+        }
+
+        @Override
+        public NotificationCompatBase.UnreadConversation getUnreadConversationFromBundle(
+                Bundle b, NotificationCompatBase.UnreadConversation.Factory factory,
+                RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+            return NotificationCompatApi21.getUnreadConversationFromBundle(
+                    b, factory, remoteInputFactory);
+        }
     }
 
     private static void addActionsToBuilder(NotificationBuilderWithActions builder,
@@ -1789,6 +1818,7 @@
         /**
          * Get additional metadata carried around with this Action.
          */
+        @Override
         public Bundle getExtras() {
             return mExtras;
         }
@@ -1797,6 +1827,7 @@
          * Get the list of inputs to be collected from the user when this action is sent.
          * May return null if no remote inputs were added.
          */
+        @Override
         public RemoteInput[] getRemoteInputs() {
             return mRemoteInputs;
         }
@@ -2225,6 +2256,19 @@
          */
         public static final int SIZE_FULL_SCREEN = 5;
 
+        /**
+         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a
+         * short amount of time when this notification is displayed on the screen. This
+         * is the default value.
+         */
+        public static final int SCREEN_TIMEOUT_SHORT = 0;
+
+        /**
+         * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on
+         * for a longer amount of time when this notification is displayed on the screen.
+         */
+        public static final int SCREEN_TIMEOUT_LONG = -1;
+
         /** Notification extra which contains wearable extensions */
         private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
 
@@ -2240,12 +2284,14 @@
         private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
         private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
         private static final String KEY_GRAVITY = "gravity";
+        private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
 
         // Flags bitwise-ored to mFlags
         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
         private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
+        private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
 
         // Default value for flags integer
         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -2264,6 +2310,7 @@
         private int mCustomSizePreset = SIZE_DEFAULT;
         private int mCustomContentHeight;
         private int mGravity = DEFAULT_GRAVITY;
+        private int mHintScreenTimeout;
 
         /**
          * Create a {@link NotificationCompat.WearableExtender} with default
@@ -2302,6 +2349,7 @@
                         SIZE_DEFAULT);
                 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
+                mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
             }
         }
 
@@ -2351,6 +2399,9 @@
             if (mGravity != DEFAULT_GRAVITY) {
                 wearableBundle.putInt(KEY_GRAVITY, mGravity);
             }
+            if (mHintScreenTimeout != 0) {
+                wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
+            }
 
             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
             return builder;
@@ -2370,6 +2421,7 @@
             that.mCustomSizePreset = this.mCustomSizePreset;
             that.mCustomContentHeight = this.mCustomContentHeight;
             that.mGravity = this.mGravity;
+            that.mHintScreenTimeout = this.mHintScreenTimeout;
             return that;
         }
 
@@ -2765,6 +2817,52 @@
             return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
         }
 
+        /**
+         * Set a hint that this notification's background should not be clipped if possible,
+         * and should instead be resized to fully display on the screen, retaining the aspect
+         * ratio of the image. This can be useful for images like barcodes or qr codes.
+         * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible.
+         * @return this object for method chaining
+         */
+        public WearableExtender setHintAvoidBackgroundClipping(
+                boolean hintAvoidBackgroundClipping) {
+            setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping);
+            return this;
+        }
+
+        /**
+         * Get a hint that this notification's background should not be clipped if possible,
+         * and should instead be resized to fully display on the screen, retaining the aspect
+         * ratio of the image. This can be useful for images like barcodes or qr codes.
+         * @return {@code true} if it's ok if the background is clipped on the screen, false
+         * otherwise. The default value is {@code false} if this was never set.
+         */
+        public boolean getHintAvoidBackgroundClipping() {
+            return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0;
+        }
+
+        /**
+         * Set a hint that the screen should remain on for at least this duration when
+         * this notification is displayed on the screen.
+         * @param timeout The requested screen timeout in milliseconds. Can also be either
+         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+         * @return this object for method chaining
+         */
+        public WearableExtender setHintScreenTimeout(int timeout) {
+            mHintScreenTimeout = timeout;
+            return this;
+        }
+
+        /**
+         * Get the duration, in milliseconds, that the screen should remain on for
+         * when this notification is displayed.
+         * @return the duration in milliseconds if > 0, or either one of the sentinel values
+         *     {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}.
+         */
+        public int getHintScreenTimeout() {
+            return mHintScreenTimeout;
+        }
+
         private void setFlag(int mask, boolean value) {
             if (value) {
                 mFlags |= mask;
@@ -2775,6 +2873,360 @@
     }
 
     /**
+     * <p>Helper class to add Android Auto extensions to notifications. To create a notification
+     * with car extensions:
+     *
+     * <ol>
+     *  <li>Create an {@link NotificationCompat.Builder}, setting any desired
+     *  properties.
+     *  <li>Create a {@link CarExtender}.
+     *  <li>Set car-specific properties using the {@code add} and {@code set} methods of
+     *  {@link CarExtender}.
+     *  <li>Call {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
+     *  to apply the extensions to a notification.
+     *  <li>Post the notification to the notification system with the
+     *  {@code NotificationManagerCompat.notify(...)} methods and not the
+     *  {@code NotificationManager.notify(...)} methods.
+     * </ol>
+     *
+     * <pre class="prettyprint">
+     * Notification notification = new NotificationCompat.Builder(context)
+     *         ...
+     *         .extend(new CarExtender()
+     *                 .set*(...))
+     *         .build();
+     * </pre>
+     *
+     * <p>Car extensions can be accessed on an existing notification by using the
+     * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
+     * to access values.
+     */
+    public static final class CarExtender implements Extender {
+        private static final String TAG = "CarExtender";
+
+        private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
+        private static final String EXTRA_LARGE_ICON = "large_icon";
+        private static final String EXTRA_CONVERSATION = "car_conversation";
+        private static final String EXTRA_COLOR = "app_color";
+
+        private Bitmap mLargeIcon;
+        private UnreadConversation mUnreadConversation;
+        private int mColor = NotificationCompat.COLOR_DEFAULT;
+
+        /**
+         * Create a {@link CarExtender} with default options.
+         */
+        public CarExtender() {
+        }
+
+        /**
+         * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
+         *
+         * @param notif The notification from which to copy options.
+         */
+        public CarExtender(Notification notif) {
+            if (Build.VERSION.SDK_INT < 21) {
+                return;
+            }
+
+            Bundle carBundle = getExtras(notif)==null ?
+                    null : getExtras(notif).getBundle(EXTRA_CAR_EXTENDER);
+            if (carBundle != null) {
+                mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
+                mColor = carBundle.getInt(EXTRA_COLOR, NotificationCompat.COLOR_DEFAULT);
+
+                Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
+                mUnreadConversation = (UnreadConversation) IMPL.getUnreadConversationFromBundle(
+                        b, UnreadConversation.FACTORY, RemoteInput.FACTORY);
+            }
+        }
+
+        /**
+         * Apply car extensions to a notification that is being built. This is typically called by
+         * the {@link android.support.v4.app.NotificationCompat.Builder#extend(NotificationCompat.Extender)}
+         * method of {@link NotificationCompat.Builder}.
+         */
+        @Override
+        public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
+            if (Build.VERSION.SDK_INT < 21) {
+                return builder;
+            }
+
+            Bundle carExtensions = new Bundle();
+
+            if (mLargeIcon != null) {
+                carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+            }
+            if (mColor != NotificationCompat.COLOR_DEFAULT) {
+                carExtensions.putInt(EXTRA_COLOR, mColor);
+            }
+
+            if (mUnreadConversation != null) {
+                Bundle b = IMPL.getBundleForUnreadConversation(mUnreadConversation);
+                carExtensions.putBundle(EXTRA_CONVERSATION, b);
+            }
+
+            builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
+            return builder;
+        }
+
+        /**
+         * Sets the accent color to use when Android Auto presents the notification.
+         *
+         * Android Auto uses the color set with {@link android.support.v4.app.NotificationCompat.Builder#setColor(int)}
+         * to accent the displayed notification. However, not all colors are acceptable in an
+         * automotive setting. This method can be used to override the color provided in the
+         * notification in such a situation.
+         */
+        public CarExtender setColor(int color) {
+            mColor = color;
+            return this;
+        }
+
+        /**
+         * Gets the accent color.
+         *
+         * @see setColor
+         */
+        public int getColor() {
+            return mColor;
+        }
+
+        /**
+         * Sets the large icon of the car notification.
+         *
+         * If no large icon is set in the extender, Android Auto will display the icon
+         * specified by {@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon(android.graphics.Bitmap)}
+         *
+         * @param largeIcon The large icon to use in the car notification.
+         * @return This object for method chaining.
+         */
+        public CarExtender setLargeIcon(Bitmap largeIcon) {
+            mLargeIcon = largeIcon;
+            return this;
+        }
+
+        /**
+         * Gets the large icon used in this car notification, or null if no icon has been set.
+         *
+         * @return The large icon for the car notification.
+         * @see CarExtender#setLargeIcon
+         */
+        public Bitmap getLargeIcon() {
+            return mLargeIcon;
+        }
+
+        /**
+         * Sets the unread conversation in a message notification.
+         *
+         * @param unreadConversation The unread part of the conversation this notification conveys.
+         * @return This object for method chaining.
+         */
+        public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
+            mUnreadConversation = unreadConversation;
+            return this;
+        }
+
+        /**
+         * Returns the unread conversation conveyed by this notification.
+         * @see #setUnreadConversation(UnreadConversation)
+         */
+        public UnreadConversation getUnreadConversation() {
+            return mUnreadConversation;
+        }
+
+        /**
+         * A class which holds the unread messages from a conversation.
+         */
+        public static class UnreadConversation extends NotificationCompatBase.UnreadConversation {
+            private final String[] mMessages;
+            private final RemoteInput mRemoteInput;
+            private final PendingIntent mReplyPendingIntent;
+            private final PendingIntent mReadPendingIntent;
+            private final String[] mParticipants;
+            private final long mLatestTimestamp;
+
+            UnreadConversation(String[] messages, RemoteInput remoteInput,
+                    PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+                    String[] participants, long latestTimestamp) {
+                mMessages = messages;
+                mRemoteInput = remoteInput;
+                mReadPendingIntent = readPendingIntent;
+                mReplyPendingIntent = replyPendingIntent;
+                mParticipants = participants;
+                mLatestTimestamp = latestTimestamp;
+            }
+
+            /**
+             * Gets the list of messages conveyed by this notification.
+             */
+            @Override
+            public String[] getMessages() {
+                return mMessages;
+            }
+
+            /**
+             * Gets the remote input that will be used to convey the response to a message list, or
+             * null if no such remote input exists.
+             */
+            @Override
+            public RemoteInput getRemoteInput() {
+                return mRemoteInput;
+            }
+
+            /**
+             * Gets the pending intent that will be triggered when the user replies to this
+             * notification.
+             */
+            @Override
+            public PendingIntent getReplyPendingIntent() {
+                return mReplyPendingIntent;
+            }
+
+            /**
+             * Gets the pending intent that Android Auto will send after it reads aloud all messages
+             * in this object's message list.
+             */
+            @Override
+            public PendingIntent getReadPendingIntent() {
+                return mReadPendingIntent;
+            }
+
+            /**
+             * Gets the participants in the conversation.
+             */
+            @Override
+            public String[] getParticipants() {
+                return mParticipants;
+            }
+
+            /**
+             * Gets the firs participant in the conversation.
+             */
+            @Override
+            public String getParticipant() {
+                return mParticipants.length > 0 ? mParticipants[0] : null;
+            }
+
+            /**
+             * Gets the timestamp of the conversation.
+             */
+            @Override
+            public long getLatestTimestamp() {
+                return mLatestTimestamp;
+            }
+
+            /** @hide */
+            static final Factory FACTORY = new Factory() {
+                @Override
+                public UnreadConversation build(
+                        String[] messages, RemoteInputCompatBase.RemoteInput remoteInput,
+                        PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+                        String[] participants, long latestTimestamp) {
+                    return new UnreadConversation(
+                            messages, (RemoteInput) remoteInput, replyPendingIntent,
+                            readPendingIntent,
+                            participants, latestTimestamp);
+                }
+            };
+
+            /**
+             * Builder class for {@link CarExtender.UnreadConversation} objects.
+             */
+            public static class Builder {
+                private final List<String> mMessages = new ArrayList<String>();
+                private final String mParticipant;
+                private RemoteInput mRemoteInput;
+                private PendingIntent mReadPendingIntent;
+                private PendingIntent mReplyPendingIntent;
+                private long mLatestTimestamp;
+
+                /**
+                 * Constructs a new builder for {@link CarExtender.UnreadConversation}.
+                 *
+                 * @param name The name of the other participant in the conversation.
+                 */
+                public Builder(String name) {
+                    mParticipant = name;
+                }
+
+                /**
+                 * Appends a new unread message to the list of messages for this conversation.
+                 *
+                 * The messages should be added from oldest to newest.
+                 *
+                 * @param message The text of the new unread message.
+                 * @return This object for method chaining.
+                 */
+                public Builder addMessage(String message) {
+                    mMessages.add(message);
+                    return this;
+                }
+
+                /**
+                 * Sets the pending intent and remote input which will convey the reply to this
+                 * notification.
+                 *
+                 * @param pendingIntent The pending intent which will be triggered on a reply.
+                 * @param remoteInput The remote input parcelable which will carry the reply.
+                 * @return This object for method chaining.
+                 *
+                 * @see CarExtender.UnreadConversation#getRemoteInput
+                 * @see CarExtender.UnreadConversation#getReplyPendingIntent
+                 */
+                public Builder setReplyAction(
+                        PendingIntent pendingIntent, RemoteInput remoteInput) {
+                    mRemoteInput = remoteInput;
+                    mReplyPendingIntent = pendingIntent;
+
+                    return this;
+                }
+
+                /**
+                 * Sets the pending intent that will be sent once the messages in this notification
+                 * are read.
+                 *
+                 * @param pendingIntent The pending intent to use.
+                 * @return This object for method chaining.
+                 */
+                public Builder setReadPendingIntent(PendingIntent pendingIntent) {
+                    mReadPendingIntent = pendingIntent;
+                    return this;
+                }
+
+                /**
+                 * Sets the timestamp of the most recent message in an unread conversation.
+                 *
+                 * If a messaging notification has been posted by your application and has not
+                 * yet been cancelled, posting a later notification with the same id and tag
+                 * but without a newer timestamp may result in Android Auto not displaying a
+                 * heads up notification for the later notification.
+                 *
+                 * @param timestamp The timestamp of the most recent message in the conversation.
+                 * @return This object for method chaining.
+                 */
+                public Builder setLatestTimestamp(long timestamp) {
+                    mLatestTimestamp = timestamp;
+                    return this;
+                }
+
+                /**
+                 * Builds a new unread conversation object.
+                 *
+                 * @return The new unread conversation object.
+                 */
+                public UnreadConversation build() {
+                    String[] messages = mMessages.toArray(new String[mMessages.size()]);
+                    String[] participants = { mParticipant };
+                    return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
+                            mReadPendingIntent, participants, mLatestTimestamp);
+                }
+            }
+        }
+    }
+
+
+    /**
      * Get an array of Notification objects from a parcelable array bundle field.
      * Update the bundle to have a typed array so fetches in the future don't need
      * to do an array copy.
diff --git a/v4/java/android/support/v4/app/SharedElementCallback.java b/v4/java/android/support/v4/app/SharedElementCallback.java
index 545edd0..7c0de86 100644
--- a/v4/java/android/support/v4/app/SharedElementCallback.java
+++ b/v4/java/android/support/v4/app/SharedElementCallback.java
@@ -20,11 +20,15 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.Parcelable;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
 
 import java.util.List;
 import java.util.Map;
@@ -38,6 +42,10 @@
  */
 public abstract class SharedElementCallback {
     private Matrix mTempMatrix;
+    private static int MAX_IMAGE_SIZE = (1024 * 1024);
+    private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap";
+    private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType";
+    private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix";
 
     /**
      * Called immediately after the start state is set for the shared element.
@@ -138,15 +146,40 @@
      */
     public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
             RectF screenBounds) {
+        if (sharedElement instanceof ImageView) {
+            ImageView imageView = ((ImageView) sharedElement);
+            Drawable d = imageView.getDrawable();
+            Drawable bg = imageView.getBackground();
+            if (d != null && bg == null) {
+                Bitmap bitmap = createDrawableBitmap(d);
+                if (bitmap != null) {
+                    Bundle bundle = new Bundle();
+                    bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
+                    bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
+                            imageView.getScaleType().toString());
+                    if (imageView.getScaleType() == ScaleType.MATRIX) {
+                        Matrix matrix = imageView.getImageMatrix();
+                        float[] values = new float[9];
+                        matrix.getValues(values);
+                        bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
+                    }
+                    return bundle;
+                }
+            }
+        }
         int bitmapWidth = Math.round(screenBounds.width());
         int bitmapHeight = Math.round(screenBounds.height());
         Bitmap bitmap = null;
         if (bitmapWidth > 0 && bitmapHeight > 0) {
+            float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+            bitmapWidth *= scale;
+            bitmapHeight *= scale;
             if (mTempMatrix == null) {
                 mTempMatrix = new Matrix();
             }
             mTempMatrix.set(viewToGlobalMatrix);
             mTempMatrix.postTranslate(-screenBounds.left, -screenBounds.top);
+            mTempMatrix.postScale(scale, scale);
             bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
             Canvas canvas = new Canvas(bitmap);
             canvas.concat(mTempMatrix);
@@ -156,6 +189,35 @@
     }
 
     /**
+     * Get a copy of bitmap of given drawable.
+     */
+    private static Bitmap createDrawableBitmap(Drawable drawable) {
+        int width = drawable.getIntrinsicWidth();
+        int height = drawable.getIntrinsicHeight();
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+        float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (width * height));
+        if (drawable instanceof BitmapDrawable && scale == 1f) {
+            // return same bitmap if scale down not needed
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+        int bitmapWidth = (int) (width * scale);
+        int bitmapHeight = (int) (height * scale);
+        Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        Rect existingBounds = drawable.getBounds();
+        int left = existingBounds.left;
+        int top = existingBounds.top;
+        int right = existingBounds.right;
+        int bottom = existingBounds.bottom;
+        drawable.setBounds(0, 0, bitmapWidth, bitmapHeight);
+        drawable.draw(canvas);
+        drawable.setBounds(left, top, right, bottom);
+        return bitmap;
+    }
+
+    /**
      * Reconstitutes a snapshot View from a Parcelable returned in
      * {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix,
      * android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List,
@@ -174,7 +236,24 @@
      */
     public View onCreateSnapshotView(Context context, Parcelable snapshot) {
         ImageView view = null;
-        if (snapshot instanceof Bitmap) {
+        if (snapshot instanceof Bundle) {
+            Bundle bundle = (Bundle) snapshot;
+            Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
+            if (bitmap == null) {
+                return null;
+            }
+            ImageView imageView = new ImageView(context);
+            view = imageView;
+            imageView.setImageBitmap(bitmap);
+            imageView.setScaleType(
+                    ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
+            if (imageView.getScaleType() == ScaleType.MATRIX) {
+                float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
+                Matrix matrix = new Matrix();
+                matrix.setValues(values);
+                imageView.setImageMatrix(matrix);
+            }
+        } else if (snapshot instanceof Bitmap) {
             Bitmap bitmap = (Bitmap) snapshot;
             view = new ImageView(context);
             view.setImageBitmap(bitmap);
diff --git a/v4/java/android/support/v4/content/res/ResourcesCompat.java b/v4/java/android/support/v4/content/res/ResourcesCompat.java
index 2dbd334..252977b 100644
--- a/v4/java/android/support/v4/content/res/ResourcesCompat.java
+++ b/v4/java/android/support/v4/content/res/ResourcesCompat.java
@@ -39,12 +39,14 @@
      * @param id The desired resource identifier, as generated by the aapt
      *           tool. This integer encodes the package, type, and resource
      *           entry. The value 0 is an invalid identifier.
-     * @param theme The theme used to style the drawable attributes, may be {@code null}.
+     * @param theme The theme used to style the drawable attributes, may be
+     *              {@code null}.
      * @return Drawable An object that can be used to draw this resource.
      * @throws NotFoundException Throws NotFoundException if the given ID does
      *         not exist.
      */
-    public Drawable getDrawable(Resources res, int id, Theme theme)
+    @SuppressWarnings("deprecation")
+    public static Drawable getDrawable(Resources res, int id, Theme theme)
             throws NotFoundException {
         final int version = Build.VERSION.SDK_INT;
         if (version >= 21) {
@@ -53,4 +55,39 @@
             return res.getDrawable(id);
         }
     }
+
+
+    /**
+     * Return a drawable object associated with a particular resource ID for
+     * the given screen density in DPI and styled for the specified theme.
+     * <p>
+     * Prior to API level 15, the theme and density will not be applied and
+     * this method simply calls through to {@link Resources#getDrawable(int)}.
+     * <p>
+     * Prior to API level 21, the theme will not be applied and this method
+     * calls through to Resources.getDrawableForDensity(int, int).
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param density The desired screen density indicated by the resource as
+     *                found in {@link android.util.DisplayMetrics}.
+     * @param theme The theme used to style the drawable attributes, may be
+     *              {@code null}.
+     * @return Drawable An object that can be used to draw this resource.
+     * @throws NotFoundException Throws NotFoundException if the given ID does
+     *             not exist.
+     */
+    @SuppressWarnings("deprecation")
+    public static Drawable getDrawableForDensity(Resources res, int id, int density, Theme theme)
+            throws NotFoundException {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ResourcesCompatApi21.getDrawableForDensity(res, id, density, theme);
+        } else if (version >= 15) {
+            return ResourcesCompatIcsMr1.getDrawableForDensity(res, id, density);
+        } else {
+            return res.getDrawable(id);
+        }
+    }
 }
diff --git a/v7/palette/src/android/support/v7/graphics/ColorUtils.java b/v4/java/android/support/v4/graphics/ColorUtils.java
similarity index 62%
rename from v7/palette/src/android/support/v7/graphics/ColorUtils.java
rename to v4/java/android/support/v4/graphics/ColorUtils.java
index 84d330b..91c61af 100644
--- a/v7/palette/src/android/support/v7/graphics/ColorUtils.java
+++ b/v4/java/android/support/v4/graphics/ColorUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 The Android Open Source Project
+ * 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.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package android.support.v7.graphics;
+package android.support.v4.graphics;
 
 import android.graphics.Color;
 
-final class ColorUtils {
+/**
+ * A set of color-related utility methods, building upon those available in {@code Color}.
+ */
+public class ColorUtils {
 
     private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
     private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
@@ -28,14 +31,17 @@
     /**
      * Composite two potentially translucent colors over each other and returns the result.
      */
-    private static int compositeColors(int fg, int bg) {
-        final float alpha1 = Color.alpha(fg) / 255f;
-        final float alpha2 = Color.alpha(bg) / 255f;
+    public static int compositeColors(int foreground, int background) {
+        final float alpha1 = Color.alpha(foreground) / 255f;
+        final float alpha2 = Color.alpha(background) / 255f;
 
         float a = (alpha1 + alpha2) * (1f - alpha1);
-        float r = (Color.red(fg) * alpha1) + (Color.red(bg) * alpha2 * (1f - alpha1));
-        float g = (Color.green(fg) * alpha1) + (Color.green(bg) * alpha2 * (1f - alpha1));
-        float b = (Color.blue(fg) * alpha1) + (Color.blue(bg) * 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));
 
         return Color.argb((int) a, (int) r, (int) g, (int) b);
     }
@@ -45,7 +51,7 @@
      *
      * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
      */
-    private static double calculateLuminance(int color) {
+    public static double calculateLuminance(int color) {
         double red = Color.red(color) / 255d;
         red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
 
@@ -59,11 +65,13 @@
     }
 
     /**
-     * Returns the contrast ratio between two colors.
-     *
-     * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+     * Returns the contrast ratio between {@code foreground} and {@code background}.
+     * {@code background} must be opaque.
+     * <p>
+     * Formula defined
+     * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
      */
-    private static double calculateContrast(int foreground, int background) {
+    public static double calculateContrast(int foreground, int background) {
         if (Color.alpha(background) != 255) {
             throw new IllegalArgumentException("background can not be translucent");
         }
@@ -80,18 +88,23 @@
     }
 
     /**
-     * Finds the minimum alpha value which can be applied to {@code foreground} so that is has a
-     * contrast value of at least {@code minContrastRatio} when compared to background.
+     * Calculates the minimum alpha value which can be applied to {@code foreground} so that would
+     * have a contrast value of at least {@code minContrastRatio} when compared to
+     * {@code background}.
      *
-     * @return the alpha value in the range 0-255.
+     * @param foreground       the foreground color.
+     * @param background       the background color. Should be opaque.
+     * @param minContrastRatio the minimum contrast ratio.
+     * @return the alpha value in the range 0-255, or -1 if no value could be calculated.
      */
-    private static int findMinimumAlpha(int foreground, int background, double minContrastRatio) {
+    public static int calculateMinimumAlpha(int foreground, int background,
+            float minContrastRatio) {
         if (Color.alpha(background) != 255) {
             throw new IllegalArgumentException("background can not be translucent");
         }
 
         // First lets check that a fully opaque foreground has sufficient contrast
-        int testForeground = modifyAlpha(foreground, 255);
+        int testForeground = setAlphaComponent(foreground, 255);
         double testRatio = calculateContrast(testForeground, background);
         if (testRatio < minContrastRatio) {
             // Fully opaque foreground does not have sufficient contrast, return error
@@ -107,7 +120,7 @@
                 (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
             final int testAlpha = (minAlpha + maxAlpha) / 2;
 
-            testForeground = modifyAlpha(foreground, testAlpha);
+            testForeground = setAlphaComponent(foreground, testAlpha);
             testRatio = calculateContrast(testForeground, background);
 
             if (testRatio < minContrastRatio) {
@@ -123,29 +136,20 @@
         return maxAlpha;
     }
 
-    static int getTextColorForBackground(int backgroundColor, float minContrastRatio) {
-        // First we will check white as most colors will be dark
-        final int whiteMinAlpha = ColorUtils
-                .findMinimumAlpha(Color.WHITE, backgroundColor, minContrastRatio);
-
-        if (whiteMinAlpha >= 0) {
-            return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
-        }
-
-        // If we hit here then there is not an translucent white which provides enough contrast,
-        // so check black
-        final int blackMinAlpha = ColorUtils
-                .findMinimumAlpha(Color.BLACK, backgroundColor, minContrastRatio);
-
-        if (blackMinAlpha >= 0) {
-            return ColorUtils.modifyAlpha(Color.BLACK, blackMinAlpha);
-        }
-
-        // This should not happen!
-        return -1;
-    }
-
-    static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+    /**
+     * Convert RGB components to HSL (hue-saturation-lightness).
+     * <ul>
+     * <li>hsl[0] is Hue [0 .. 360)</li>
+     * <li>hsl[1] is Saturation [0...1]</li>
+     * <li>hsl[2] is Lightness [0...1]</li>
+     * </ul>
+     *
+     * @param r   red component value [0..255]
+     * @param g   green component value [0..255]
+     * @param b   blue component value [0..255]
+     * @param hsl 3 element array which holds the resulting HSL components.
+     */
+    public static void RGBToHSL(int r, int g, int b, float[] hsl) {
         final float rf = r / 255f;
         final float gf = g / 255f;
         final float bf = b / 255f;
@@ -169,7 +173,7 @@
                 h = ((rf - gf) / deltaMaxMin) + 4f;
             }
 
-            s =  deltaMaxMin / (1f - Math.abs(2f * l - 1f));
+            s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
         }
 
         hsl[0] = (h * 60f) % 360f;
@@ -177,7 +181,34 @@
         hsl[2] = l;
     }
 
-    static int HSLtoRGB (float[] hsl) {
+    /**
+     * Convert the ARGB color to its HSL (hue-saturation-lightness) components.
+     * <ul>
+     * <li>hsl[0] is Hue [0 .. 360)</li>
+     * <li>hsl[1] is Saturation [0...1]</li>
+     * <li>hsl[2] is Lightness [0...1]</li>
+     * </ul>
+     *
+     * @param color the ARGB color to convert. The alpha component is ignored.
+     * @param hsl 3 element array which holds the resulting HSL components.
+     */
+    public static void colorToHSL(int color, float[] hsl) {
+        RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
+    }
+
+    /**
+     * Convert HSL (hue-saturation-lightness) components to a RGB color.
+     * <ul>
+     * <li>hsl[0] is Hue [0 .. 360)</li>
+     * <li>hsl[1] is Saturation [0...1]</li>
+     * <li>hsl[2] is Lightness [0...1]</li>
+     * </ul>
+     * If hsv values are out of range, they are pinned.
+     *
+     * @param hsl 3 element array which holds the input HSL components.
+     * @return the resulting RGB color
+     */
+    public static int HSLToColor(float[] hsl) {
         final float h = hsl[0];
         final float s = hsl[1];
         final float l = hsl[2];
@@ -234,7 +265,10 @@
     /**
      * Set the alpha component of {@code color} to be {@code alpha}.
      */
-    static int modifyAlpha(int color, int alpha) {
+    public static int setAlphaComponent(int color, int alpha) {
+        if (alpha < 0 || alpha > 255) {
+            throw new IllegalArgumentException("alpha must be between 0 and 255.");
+        }
         return (color & 0x00ffffff) | (alpha << 24);
     }
 
diff --git a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
index c6f0d2a..99c762f 100644
--- a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
+++ b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
@@ -19,7 +19,6 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 
 /**
  * Helper for accessing features in {@link android.graphics.drawable.Drawable}
@@ -38,6 +37,7 @@
         void setTint(Drawable drawable, int tint);
         void setTintList(Drawable drawable, ColorStateList tint);
         void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
+        Drawable wrap(Drawable drawable);
     }
 
     /**
@@ -67,14 +67,22 @@
 
         @Override
         public void setTint(Drawable drawable, int tint) {
+            DrawableCompatBase.setTint(drawable, tint);
         }
 
         @Override
         public void setTintList(Drawable drawable, ColorStateList tint) {
+            DrawableCompatBase.setTintList(drawable, tint);
         }
 
         @Override
         public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
+            DrawableCompatBase.setTintMode(drawable, tintMode);
+        }
+
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatBase.wrapForTinting(drawable);
         }
     }
 
@@ -86,6 +94,11 @@
         public void jumpToCurrentState(Drawable drawable) {
             DrawableCompatHoneycomb.jumpToCurrentState(drawable);
         }
+
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatHoneycomb.wrapForTinting(drawable);
+        }
     }
 
     /**
@@ -101,35 +114,55 @@
         public boolean isAutoMirrored(Drawable drawable) {
             return DrawableCompatKitKat.isAutoMirrored(drawable);
         }
+
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatKitKat.wrapForTinting(drawable);
+        }
     }
 
     /**
      * Interface implementation for devices with at least L APIs.
      */
-    static class LDrawableImpl extends KitKatDrawableImpl {
+    static class LollipopDrawableImpl extends KitKatDrawableImpl {
         @Override
         public void setHotspot(Drawable drawable, float x, float y) {
-            DrawableCompatL.setHotspot(drawable, x, y);
+            DrawableCompatLollipop.setHotspot(drawable, x, y);
         }
 
         @Override
         public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
-            DrawableCompatL.setHotspotBounds(drawable, left, top, right, bottom);
+            DrawableCompatLollipop.setHotspotBounds(drawable, left, top, right, bottom);
         }
 
         @Override
         public void setTint(Drawable drawable, int tint) {
-            DrawableCompatL.setTint(drawable, tint);
+            DrawableCompatLollipop.setTint(drawable, tint);
         }
 
         @Override
         public void setTintList(Drawable drawable, ColorStateList tint) {
-            DrawableCompatL.setTintList(drawable, tint);
+            DrawableCompatLollipop.setTintList(drawable, tint);
         }
 
         @Override
         public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
-            DrawableCompatL.setTintMode(drawable, tintMode);
+            DrawableCompatLollipop.setTintMode(drawable, tintMode);
+        }
+
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatLollipop.wrapForTinting(drawable);
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least L APIs.
+     */
+    static class LollipopMr1DrawableImpl extends LollipopDrawableImpl {
+        @Override
+        public Drawable wrap(Drawable drawable) {
+            return DrawableCompatApi22.wrapForTinting(drawable);
         }
     }
 
@@ -139,8 +172,10 @@
     static final DrawableImpl IMPL;
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
-        if (version >= 21) {
-            IMPL = new LDrawableImpl();
+        if (version >= 22) {
+            IMPL = new LollipopMr1DrawableImpl();
+        } else if (version >= 21) {
+            IMPL = new LollipopDrawableImpl();
         } else if (version >= 19) {
             IMPL = new KitKatDrawableImpl();
         } else if (version >= 11) {
@@ -219,7 +254,7 @@
      * Specifies a tint for {@code drawable}.
      *
      * @param drawable The Drawable against which to invoke the method.
-     * @param tint Color to use for tinting this drawable
+     * @param tint     Color to use for tinting this drawable
      */
     public static void setTint(Drawable drawable, int tint) {
         IMPL.setTint(drawable, tint);
@@ -229,8 +264,7 @@
      * Specifies a tint for {@code drawable} as a color state list.
      *
      * @param drawable The Drawable against which to invoke the method.
-     * @param tint Color state list to use for tinting this drawable, or null to
-     *            clear the tint
+     * @param tint     Color state list to use for tinting this drawable, or null to clear the tint
      */
     public static void setTintList(Drawable drawable, ColorStateList tint) {
         IMPL.setTintList(drawable, tint);
@@ -240,11 +274,45 @@
      * Specifies a tint blending mode for {@code drawable}.
      *
      * @param drawable The Drawable against which to invoke the method.
-     * @param tintMode Color state list to use for tinting this drawable, or null to
-     *            clear the tint
      * @param tintMode A Porter-Duff blending mode
      */
     public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
         IMPL.setTintMode(drawable, tintMode);
     }
+
+    /**
+     * Potentially wrap {@code drawable} so that it may be used for tinting across the
+     * different API levels, via the tinting methods in this class.
+     * <p>
+     * If you need to get hold of the original {@link android.graphics.drawable.Drawable} again,
+     * you can use the value returned from {@link #unwrap(Drawable)}.
+     *
+     * @param drawable The Drawable to process
+     * @return A drawable capable of being tinted across all API levels.
+     *
+     * @see #setTint(Drawable, int)
+     * @see #setTintList(Drawable, ColorStateList)
+     * @see #setTintMode(Drawable, PorterDuff.Mode)
+     * @see #unwrap(Drawable)
+     */
+    public static Drawable wrap(Drawable drawable) {
+        return IMPL.wrap(drawable);
+    }
+
+    /**
+     * Unwrap {@code drawable} if it is the result of a call to {@link #wrap(Drawable)}. If
+     * the {@code drawable} is not the result of a call to {@link #wrap(Drawable)} then
+     * {@code drawable} is returned as-is.
+     *
+     * @param drawable The drawable to unwrap
+     * @return the unwrapped {@link Drawable} or {@code drawable} if it hasn't been wrapped.
+     *
+     * @see #wrap(Drawable)
+     */
+    public static <T extends Drawable> T unwrap(Drawable drawable) {
+        if (drawable instanceof DrawableWrapper) {
+            return (T) ((DrawableWrapper) drawable).getWrappedDrawable();
+        }
+        return (T) drawable;
+    }
 }
diff --git a/v4/java/android/support/v4/media/MediaDescriptionCompat.java b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
new file mode 100644
index 0000000..39e3a24
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * A simple set of metadata for a media item suitable for display. This can be
+ * created using the Builder or retrieved from existing metadata using
+ * {@link MediaMetadataCompat#getDescription()}.
+ */
+public final class MediaDescriptionCompat implements Parcelable {
+    /**
+     * A unique persistent id for the content or null.
+     */
+    private final String mMediaId;
+    /**
+     * A primary title suitable for display or null.
+     */
+    private final CharSequence mTitle;
+    /**
+     * A subtitle suitable for display or null.
+     */
+    private final CharSequence mSubtitle;
+    /**
+     * A description suitable for display or null.
+     */
+    private final CharSequence mDescription;
+    /**
+     * A bitmap icon suitable for display or null.
+     */
+    private final Bitmap mIcon;
+    /**
+     * A Uri for an icon suitable for display or null.
+     */
+    private final Uri mIconUri;
+    /**
+     * Extras for opaque use by apps/system.
+     */
+    private final Bundle mExtras;
+
+    /**
+     * A cached copy of the equivalent framework object.
+     */
+    private Object mDescriptionObj;
+
+    private MediaDescriptionCompat(String mediaId, CharSequence title, CharSequence subtitle,
+            CharSequence description, Bitmap icon, Uri iconUri, Bundle extras) {
+        mMediaId = mediaId;
+        mTitle = title;
+        mSubtitle = subtitle;
+        mDescription = description;
+        mIcon = icon;
+        mIconUri = iconUri;
+        mExtras = extras;
+    }
+
+    private MediaDescriptionCompat(Parcel in) {
+        mMediaId = in.readString();
+        mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mIcon = in.readParcelable(null);
+        mIconUri = in.readParcelable(null);
+        mExtras = in.readBundle();
+    }
+
+    /**
+     * Returns the media id or null. See
+     * {@link MediaMetadataCompat#METADATA_KEY_MEDIA_ID}.
+     */
+    public String getMediaId() {
+        return mMediaId;
+    }
+
+    /**
+     * Returns a title suitable for display or null.
+     *
+     * @return A title or null.
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns a subtitle suitable for display or null.
+     *
+     * @return A subtitle or null.
+     */
+    public CharSequence getSubtitle() {
+        return mSubtitle;
+    }
+
+    /**
+     * Returns a description suitable for display or null.
+     *
+     * @return A description or null.
+     */
+    public CharSequence getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Returns a bitmap icon suitable for display or null.
+     *
+     * @return An icon or null.
+     */
+    public Bitmap getIconBitmap() {
+        return mIcon;
+    }
+
+    /**
+     * Returns a Uri for an icon suitable for display or null.
+     *
+     * @return An icon uri or null.
+     */
+    public Uri getIconUri() {
+        return mIconUri;
+    }
+
+    /**
+     * Returns any extras that were added to the description.
+     *
+     * @return A bundle of extras or null.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (Build.VERSION.SDK_INT < 21) {
+            dest.writeString(mMediaId);
+            TextUtils.writeToParcel(mTitle, dest, flags);
+            TextUtils.writeToParcel(mSubtitle, dest, flags);
+            TextUtils.writeToParcel(mDescription, dest, flags);
+            dest.writeParcelable(mIcon, flags);
+            dest.writeParcelable(mIconUri, flags);
+            dest.writeBundle(mExtras);
+        } else {
+            MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mTitle + ", " + mSubtitle + ", " + mDescription;
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.MediaDescription}
+     * object.
+     * <p>
+     * This method is only supported on
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+     * </p>
+     *
+     * @return An equivalent {@link android.media.MediaDescription} object, or
+     *         null if none.
+     */
+    public Object getMediaDescription() {
+        if (mDescriptionObj != null || Build.VERSION.SDK_INT < 21) {
+            return mDescriptionObj;
+        }
+        Object bob = MediaDescriptionCompatApi21.Builder.newInstance();
+        MediaDescriptionCompatApi21.Builder.setMediaId(bob, mMediaId);
+        MediaDescriptionCompatApi21.Builder.setTitle(bob, mTitle);
+        MediaDescriptionCompatApi21.Builder.setSubtitle(bob, mSubtitle);
+        MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription);
+        MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon);
+        MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri);
+        MediaDescriptionCompatApi21.Builder.setExtras(bob, mExtras);
+        mDescriptionObj = MediaDescriptionCompatApi21.Builder.build(bob);
+
+        return mDescriptionObj;
+    }
+
+    /**
+     * Creates an instance from a framework
+     * {@link android.media.MediaDescription} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @param descriptionObj A {@link android.media.MediaDescription} object, or
+     *            null if none.
+     * @return An equivalent {@link MediaMetadataCompat} object, or null if
+     *         none.
+     */
+    public static MediaDescriptionCompat fromMediaDescription(Object descriptionObj) {
+        if (descriptionObj == null || Build.VERSION.SDK_INT < 21) {
+            return null;
+        }
+
+        Builder bob = new Builder();
+        bob.setMediaId(MediaDescriptionCompatApi21.getMediaId(descriptionObj));
+        bob.setTitle(MediaDescriptionCompatApi21.getTitle(descriptionObj));
+        bob.setSubtitle(MediaDescriptionCompatApi21.getSubtitle(descriptionObj));
+        bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj));
+        bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj));
+        bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj));
+        bob.setExtras(MediaDescriptionCompatApi21.getExtras(descriptionObj));
+        MediaDescriptionCompat descriptionCompat = bob.build();
+        descriptionCompat.mDescriptionObj = descriptionObj;
+
+        return descriptionCompat;
+    }
+
+    public static final Parcelable.Creator<MediaDescriptionCompat> CREATOR =
+            new Parcelable.Creator<MediaDescriptionCompat>() {
+            @Override
+                public MediaDescriptionCompat createFromParcel(Parcel in) {
+                    if (Build.VERSION.SDK_INT < 21) {
+                        return new MediaDescriptionCompat(in);
+                    } else {
+                        return fromMediaDescription(MediaDescriptionCompatApi21.fromParcel(in));
+                    }
+                }
+
+            @Override
+                public MediaDescriptionCompat[] newArray(int size) {
+                    return new MediaDescriptionCompat[size];
+                }
+            };
+
+    /**
+     * Builder for {@link MediaDescriptionCompat} objects.
+     */
+    public static final class Builder {
+        private String mMediaId;
+        private CharSequence mTitle;
+        private CharSequence mSubtitle;
+        private CharSequence mDescription;
+        private Bitmap mIcon;
+        private Uri mIconUri;
+        private Bundle mExtras;
+
+        /**
+         * Creates an initially empty builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Sets the media id.
+         *
+         * @param mediaId The unique id for the item or null.
+         * @return this
+         */
+        public Builder setMediaId(String mediaId) {
+            mMediaId = mediaId;
+            return this;
+        }
+
+        /**
+         * Sets the title.
+         *
+         * @param title A title suitable for display to the user or null.
+         * @return this
+         */
+        public Builder setTitle(CharSequence title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the subtitle.
+         *
+         * @param subtitle A subtitle suitable for display to the user or null.
+         * @return this
+         */
+        public Builder setSubtitle(CharSequence subtitle) {
+            mSubtitle = subtitle;
+            return this;
+        }
+
+        /**
+         * Sets the description.
+         *
+         * @param description A description suitable for display to the user or
+         *            null.
+         * @return this
+         */
+        public Builder setDescription(CharSequence description) {
+            mDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets the icon.
+         *
+         * @param icon A {@link Bitmap} icon suitable for display to the user or
+         *            null.
+         * @return this
+         */
+        public Builder setIconBitmap(Bitmap icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the icon uri.
+         *
+         * @param iconUri A {@link Uri} for an icon suitable for display to the
+         *            user or null.
+         * @return this
+         */
+        public Builder setIconUri(Uri iconUri) {
+            mIconUri = iconUri;
+            return this;
+        }
+
+        /**
+         * Sets a bundle of extras.
+         *
+         * @param extras The extras to include with this description or null.
+         * @return this
+         */
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Creates a {@link MediaDescriptionCompat} instance with the specified
+         * fields.
+         *
+         * @return A MediaDescriptionCompat instance.
+         */
+        public MediaDescriptionCompat build() {
+            return new MediaDescriptionCompat(mMediaId, mTitle, mSubtitle, mDescription, mIcon,
+                    mIconUri, mExtras);
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.aidl b/v4/java/android/support/v4/media/MediaMetadataCompat.aidl
new file mode 100644
index 0000000..6d36b97
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media;
+
+parcelable MediaMetadataCompat;
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.java b/v4/java/android/support/v4/media/MediaMetadataCompat.java
index d289cad..3807480 100644
--- a/v4/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.java
@@ -16,11 +16,13 @@
 package android.support.v4.media;
 
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.v4.util.ArrayMap;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.util.Set;
@@ -184,6 +186,13 @@
     public static final String METADATA_KEY_DISPLAY_ICON_URI
             = "android.media.metadata.DISPLAY_ICON_URI";
 
+    /**
+     * A String key for identifying the content. This value is specific to the
+     * service providing the content. If used, this should be a persistent
+     * unique key for the underlying content.
+     */
+    public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+
     private static final int METADATA_TYPE_LONG = 0;
     private static final int METADATA_TYPE_TEXT = 1;
     private static final int METADATA_TYPE_BITMAP = 2;
@@ -218,10 +227,34 @@
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT);
     }
 
+    private static final String[] PREFERRED_DESCRIPTION_ORDER = {
+            METADATA_KEY_TITLE,
+            METADATA_KEY_ARTIST,
+            METADATA_KEY_ALBUM,
+            METADATA_KEY_ALBUM_ARTIST,
+            METADATA_KEY_WRITER,
+            METADATA_KEY_AUTHOR,
+            METADATA_KEY_COMPOSER
+    };
+
+    private static final String[] PREFERRED_BITMAP_ORDER = {
+            METADATA_KEY_DISPLAY_ICON,
+            METADATA_KEY_ART,
+            METADATA_KEY_ALBUM_ART
+    };
+
+    private static final String[] PREFERRED_URI_ORDER = {
+            METADATA_KEY_DISPLAY_ICON_URI,
+            METADATA_KEY_ART_URI,
+            METADATA_KEY_ALBUM_ART_URI
+    };
+
     private final Bundle mBundle;
     private Object mMetadataObj;
+    private MediaDescriptionCompat mDescription;
 
     private MediaMetadataCompat(Bundle bundle) {
         mBundle = new Bundle(bundle);
@@ -316,6 +349,73 @@
         return bmp;
     }
 
+    /**
+     * Returns a simple description of this metadata for display purposes.
+     *
+     * @return A simple description of this metadata.
+     */
+    public MediaDescriptionCompat getDescription() {
+        if (mDescription != null) {
+            return mDescription;
+        }
+
+        String mediaId = getString(METADATA_KEY_MEDIA_ID);
+
+        CharSequence[] text = new CharSequence[3];
+        Bitmap icon = null;
+        Uri iconUri = null;
+
+        // First handle the case where display data is set already
+        CharSequence displayText = getText(METADATA_KEY_DISPLAY_TITLE);
+        if (!TextUtils.isEmpty(displayText)) {
+            // If they have a display title use only display data, otherwise use
+            // our best bets
+            text[0] = displayText;
+            text[1] = getText(METADATA_KEY_DISPLAY_SUBTITLE);
+            text[2] = getText(METADATA_KEY_DISPLAY_DESCRIPTION);
+        } else {
+            // Use whatever fields we can
+            int textIndex = 0;
+            int keyIndex = 0;
+            while (textIndex < text.length && keyIndex < PREFERRED_DESCRIPTION_ORDER.length) {
+                CharSequence next = getText(PREFERRED_DESCRIPTION_ORDER[keyIndex++]);
+                if (!TextUtils.isEmpty(next)) {
+                    // Fill in the next empty bit of text
+                    text[textIndex++] = next;
+                }
+            }
+        }
+
+        // Get the best art bitmap we can find
+        for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) {
+            Bitmap next = getBitmap(PREFERRED_BITMAP_ORDER[i]);
+            if (next != null) {
+                icon = next;
+                break;
+            }
+        }
+
+        // Get the best Uri we can find
+        for (int i = 0; i < PREFERRED_URI_ORDER.length; i++) {
+            String next = getString(PREFERRED_URI_ORDER[i]);
+            if (!TextUtils.isEmpty(next)) {
+                iconUri = Uri.parse(next);
+                break;
+            }
+        }
+
+        MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder();
+        bob.setMediaId(mediaId);
+        bob.setTitle(text[0]);
+        bob.setSubtitle(text[1]);
+        bob.setDescription(text[2]);
+        bob.setIconBitmap(icon);
+        bob.setIconUri(iconUri);
+        mDescription = bob.build();
+
+        return mDescription;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -345,13 +445,27 @@
     }
 
     /**
-     * Creates an instance from a framework {@link android.media.MediaMetadata} object.
+     * Gets the bundle backing the metadata object. This is available to support
+     * backwards compatibility. Apps should not modify the bundle directly.
+     *
+     * @return The Bundle backing this metadata.
+     */
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Creates an instance from a framework {@link android.media.MediaMetadata}
+     * object.
      * <p>
-     * This method is only supported on API 21+.
+     * This method is only supported on
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
      * </p>
      *
-     * @param metadataObj A {@link android.media.MediaMetadata} object, or null if none.
-     * @return An equivalent {@link MediaMetadataCompat} object, or null if none.
+     * @param metadataObj A {@link android.media.MediaMetadata} object, or null
+     *            if none.
+     * @return An equivalent {@link MediaMetadataCompat} object, or null if
+     *         none.
      */
     public static MediaMetadataCompat fromMediaMetadata(Object metadataObj) {
         if (metadataObj == null || Build.VERSION.SDK_INT < 21) {
@@ -390,10 +504,12 @@
     /**
      * Gets the underlying framework {@link android.media.MediaMetadata} object.
      * <p>
-     * This method is only supported on API 21+.
+     * This method is only supported on
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
      * </p>
      *
-     * @return An equivalent {@link android.media.MediaMetadata} object, or null if none.
+     * @return An equivalent {@link android.media.MediaMetadata} object, or null
+     *         if none.
      */
     public Object getMediaMetadata() {
         if (mMetadataObj != null || Build.VERSION.SDK_INT < 21) {
diff --git a/v4/java/android/support/v4/media/RatingCompat.aidl b/v4/java/android/support/v4/media/RatingCompat.aidl
new file mode 100644
index 0000000..223fd5c
--- /dev/null
+++ b/v4/java/android/support/v4/media/RatingCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media;
+
+parcelable RatingCompat;
diff --git a/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl b/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl
new file mode 100644
index 0000000..d905350
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/IMediaControllerCallback.aidl
@@ -0,0 +1,40 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+/**
+ * Callback interface for a MediaSessionCompat to send updates to a
+ * MediaControllerCompat. This is only used on pre-Lollipop systems.
+ * @hide
+ */
+oneway interface IMediaControllerCallback {
+    void onEvent(String event, in Bundle extras);
+    void onSessionDestroyed();
+
+    // These callbacks are for the TransportController
+    void onPlaybackStateChanged(in PlaybackStateCompat state);
+    void onMetadataChanged(in MediaMetadataCompat metadata);
+    void onQueueChanged(in List<MediaSessionCompat.QueueItem> queue);
+    void onQueueTitleChanged(CharSequence title);
+    void onExtrasChanged(in Bundle extras);
+    void onVolumeInfoChanged(in ParcelableVolumeInfo info);
+}
diff --git a/v4/java/android/support/v4/media/session/IMediaSession.aidl b/v4/java/android/support/v4/media/session/IMediaSession.aidl
new file mode 100644
index 0000000..1ce425d
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/IMediaSession.aidl
@@ -0,0 +1,69 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.IMediaControllerCallback;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import java.util.List;
+
+/**
+ * Interface to a MediaSessionCompat. This is only used on pre-Lollipop systems.
+ * @hide
+ */
+interface IMediaSession {
+    void sendCommand(String command, in Bundle args, in MediaSessionCompat.ResultReceiverWrapper cb);
+    boolean sendMediaButton(in KeyEvent mediaButton);
+    void registerCallbackListener(in IMediaControllerCallback cb);
+    void unregisterCallbackListener(in IMediaControllerCallback cb);
+    boolean isTransportControlEnabled();
+    String getPackageName();
+    String getTag();
+    PendingIntent getLaunchPendingIntent();
+    long getFlags();
+    ParcelableVolumeInfo getVolumeAttributes();
+    void adjustVolume(int direction, int flags, String packageName);
+    void setVolumeTo(int value, int flags, String packageName);
+
+    // These commands are for the TransportControls
+    void play();
+    void playFromMediaId(String uri, in Bundle extras);
+    void playFromSearch(String string, in Bundle extras);
+    void skipToQueueItem(long id);
+    void pause();
+    void stop();
+    void next();
+    void previous();
+    void fastForward();
+    void rewind();
+    void seekTo(long pos);
+    void rate(in RatingCompat rating);
+    void sendCustomAction(String action, in Bundle args);
+    MediaMetadataCompat getMetadata();
+    PlaybackStateCompat getPlaybackState();
+    List<MediaSessionCompat.QueueItem> getQueue();
+    CharSequence getQueueTitle();
+    Bundle getExtras();
+    int getRatingType();
+}
diff --git a/v4/java/android/support/v4/media/session/MediaControllerCompat.java b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
index 9b076949..ef99f43 100644
--- a/v4/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -16,17 +16,29 @@
 
 package android.support.v4.media.session;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.media.AudioManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.RatingCompat;
 import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.KeyEvent;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Allows an app to interact with an ongoing media session. Media buttons and
  * other commands can be sent to the session. A callback may be registered to
@@ -41,7 +53,10 @@
  * introduced after API level 4 in a backwards compatible fashion.
  */
 public final class MediaControllerCompat {
+    private static final String TAG = "MediaControllerCompat";
+
     private final MediaControllerImpl mImpl;
+    private final MediaSessionCompat.Token mToken;
 
     /**
      * Creates a media controller from a session.
@@ -52,11 +67,12 @@
         if (session == null) {
             throw new IllegalArgumentException("session must not be null");
         }
+        mToken = session.getSessionToken();
 
         if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaControllerImplApi21(context, session);
         } else {
-            mImpl = new MediaControllerImplBase();
+            mImpl = new MediaControllerImplBase(mToken);
         }
     }
 
@@ -72,11 +88,12 @@
         if (sessionToken == null) {
             throw new IllegalArgumentException("sessionToken must not be null");
         }
+        mToken = sessionToken;
 
         if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaControllerImplApi21(context, sessionToken);
         } else {
-            mImpl = new MediaControllerImplBase();
+            mImpl = new MediaControllerImplBase(mToken);
         }
     }
 
@@ -122,6 +139,30 @@
     }
 
     /**
+     * Get the current play queue for this session if one is set. If you only
+     * care about the current item {@link #getMetadata()} should be used.
+     *
+     * @return The current play queue or null.
+     */
+    public List<MediaSessionCompat.QueueItem> getQueue() {
+        return mImpl.getQueue();
+    }
+
+    /**
+     * Get the queue title for this session.
+     */
+    public CharSequence getQueueTitle() {
+        return mImpl.getQueueTitle();
+    }
+
+    /**
+     * Get the extras for this session.
+     */
+    public Bundle getExtras() {
+        return mImpl.getExtras();
+    }
+
+    /**
      * Get the rating type supported by the session. One of:
      * <ul>
      * <li>{@link RatingCompat#RATING_NONE}</li>
@@ -140,6 +181,16 @@
     }
 
     /**
+     * Get the flags for this session. Flags are defined in
+     * {@link MediaSessionCompat}.
+     *
+     * @return The current set of flags for the session.
+     */
+    public long getFlags() {
+        return mImpl.getFlags();
+    }
+
+    /**
      * Get the current playback info for this session.
      *
      * @return The current playback info or null.
@@ -149,6 +200,57 @@
     }
 
     /**
+     * Get an intent for launching UI associated with this session if one
+     * exists.
+     *
+     * @return A {@link PendingIntent} to launch UI or null.
+     */
+    public PendingIntent getSessionActivity() {
+        return mImpl.getSessionActivity();
+    }
+
+    /**
+     * Get the token for the session this controller is connected to.
+     *
+     * @return The session's token.
+     */
+    public MediaSessionCompat.Token getSessionToken() {
+        return mToken;
+    }
+
+    /**
+     * Set the volume of the output this session is playing on. The command will
+     * be ignored if it does not support
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getPlaybackInfo()
+     * @param value The value to set it to, between 0 and the reported max.
+     * @param flags Flags from {@link AudioManager} to include with the volume
+     *            request.
+     */
+    public void setVolumeTo(int value, int flags) {
+        mImpl.setVolumeTo(value, flags);
+    }
+
+    /**
+     * Adjust the volume of the output this session is playing on. The direction
+     * must be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+     * The command will be ignored if the session does not support
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
+     * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
+     * {@link AudioManager} may be used to affect the handling.
+     *
+     * @see #getPlaybackInfo()
+     * @param direction The direction to adjust the volume in.
+     * @param flags Any flags to pass with the command.
+     */
+    public void adjustVolume(int direction, int flags) {
+        mImpl.adjustVolume(direction, flags);
+    }
+
+    /**
      * Adds a callback to receive updates from the Session. Updates will be
      * posted on the caller's thread.
      *
@@ -206,13 +308,23 @@
     }
 
     /**
-     * Gets the underlying framework {@link android.media.session.MediaController} object.
+     * Get the session owner's package name.
+     *
+     * @return The package name of of the session owner.
+     */
+    public String getPackageName() {
+        return mImpl.getPackageName();
+    }
+
+    /**
+     * Gets the underlying framework
+     * {@link android.media.session.MediaController} object.
      * <p>
      * This method is only supported on API 21+.
      * </p>
      *
-     * @return The underlying {@link android.media.session.MediaController} object,
-     * or null if none.
+     * @return The underlying {@link android.media.session.MediaController}
+     *         object, or null if none.
      */
     public Object getMediaController() {
         return mImpl.getMediaController();
@@ -222,14 +334,17 @@
      * Callback for receiving updates on from the session. A Callback can be
      * registered using {@link #registerCallback}
      */
-    public static abstract class Callback {
-        final Object mCallbackObj;
+    public static abstract class Callback implements IBinder.DeathRecipient {
+        private final Object mCallbackObj;
+        private MessageHandler mHandler;
+
+        private boolean mRegistered = false;
 
         public Callback() {
             if (android.os.Build.VERSION.SDK_INT >= 21) {
                 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
             } else {
-                mCallbackObj = null;
+                mCallbackObj = new StubCompat();
             }
         }
 
@@ -268,6 +383,56 @@
         public void onMetadataChanged(MediaMetadataCompat metadata) {
         }
 
+        /**
+         * Override to handle changes to items in the queue.
+         *
+         * @see MediaSessionCompat.QueueItem
+         * @param queue A list of items in the current play queue. It should
+         *            include the currently playing item as well as previous and
+         *            upcoming items if applicable.
+         */
+        public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
+        }
+
+        /**
+         * Override to handle changes to the queue title.
+         *
+         * @param title The title that should be displayed along with the play
+         *            queue such as "Now Playing". May be null if there is no
+         *            such title.
+         */
+        public void onQueueTitleChanged(CharSequence title) {
+        }
+
+        /**
+         * Override to handle chagnes to the {@link MediaSessionCompat} extras.
+         *
+         * @param extras The extras that can include other information
+         *            associated with the {@link MediaSessionCompat}.
+         */
+        public void onExtrasChanged(Bundle extras) {
+        }
+
+        /**
+         * Override to handle changes to the audio info.
+         *
+         * @param info The current audio info for this session.
+         */
+        public void onAudioInfoChanged(PlaybackInfo info) {
+        }
+
+        @Override
+        public void binderDied() {
+            onSessionDestroyed();
+        }
+
+        /**
+         * Set the handler to use for pre 21 callbacks.
+         */
+        private void setHandler(Handler handler) {
+            mHandler = new MessageHandler(handler.getLooper());
+        }
+
         private class StubApi21 implements MediaControllerCompatApi21.Callback {
             @Override
             public void onSessionDestroyed() {
@@ -291,6 +456,106 @@
                         MediaMetadataCompat.fromMediaMetadata(metadataObj));
             }
         }
+
+        private class StubCompat extends IMediaControllerCallback.Stub {
+
+            @Override
+            public void onEvent(String event, Bundle extras) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_EVENT, event, extras);
+            }
+
+            @Override
+            public void onSessionDestroyed() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_DESTROYED, null, null);
+            }
+
+            @Override
+            public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
+            }
+
+            @Override
+            public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
+            }
+
+            @Override
+            public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
+            }
+
+            @Override
+            public void onQueueTitleChanged(CharSequence title) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
+            }
+
+            @Override
+            public void onExtrasChanged(Bundle extras) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
+            }
+
+            @Override
+            public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
+                PlaybackInfo pi = null;
+                if (info != null) {
+                    pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
+                            info.maxVolume, info.currentVolume);
+                }
+                mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
+            }
+        }
+
+        private class MessageHandler extends Handler {
+            private static final int MSG_EVENT = 1;
+            private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
+            private static final int MSG_UPDATE_METADATA = 3;
+            private static final int MSG_UPDATE_VOLUME = 4;
+            private static final int MSG_UPDATE_QUEUE = 5;
+            private static final int MSG_UPDATE_QUEUE_TITLE = 6;
+            private static final int MSG_UPDATE_EXTRAS = 7;
+            private static final int MSG_DESTROYED = 8;
+
+            public MessageHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (!mRegistered) {
+                    return;
+                }
+                switch (msg.what) {
+                    case MSG_EVENT:
+                        onSessionEvent((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_UPDATE_PLAYBACK_STATE:
+                        onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
+                        break;
+                    case MSG_UPDATE_METADATA:
+                        onMetadataChanged((MediaMetadataCompat) msg.obj);
+                        break;
+                    case MSG_UPDATE_QUEUE:
+                        onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj);
+                        break;
+                    case MSG_UPDATE_QUEUE_TITLE:
+                        onQueueTitleChanged((CharSequence) msg.obj);
+                        break;
+                    case MSG_UPDATE_EXTRAS:
+                        onExtrasChanged((Bundle) msg.obj);
+                        break;
+                    case MSG_UPDATE_VOLUME:
+                        onAudioInfoChanged((PlaybackInfo) msg.obj);
+                        break;
+                    case MSG_DESTROYED:
+                        onSessionDestroyed();
+                        break;
+                }
+            }
+
+            public void post(int what, Object obj, Bundle data) {
+                obtainMessage(what, obj).sendToTarget();
+            }
+        }
     }
 
     /**
@@ -307,6 +572,32 @@
         public abstract void play();
 
         /**
+         * Request that the player start playback for a specific {@link Uri}.
+         *
+         * @param mediaId The uri of the requested media.
+         * @param extras Optional extras that can include extra information
+         *            about the media item to be played.
+         */
+        public abstract void playFromMediaId(String mediaId, Bundle extras);
+
+        /**
+         * Request that the player start playback for a specific search query.
+         * An empty or null query should be treated as a request to play any
+         * music.
+         *
+         * @param query The search query.
+         * @param extras Optional extras that can include extra information
+         *            about the query.
+         */
+        public abstract void playFromSearch(String query, Bundle extras);
+
+        /**
+         * Play an item with a specific id in the play queue. If you specify an
+         * id that is not in the play queue, the behavior is undefined.
+         */
+        public abstract void skipToQueueItem(long id);
+
+        /**
          * Request that the player pause its playback and stay at its current
          * position.
          */
@@ -355,6 +646,30 @@
          * @param rating The rating to set for the current content
          */
         public abstract void setRating(RatingCompat rating);
+
+        /**
+         * Send a custom action for the {@link MediaSessionCompat} to perform.
+         *
+         * @param customAction The action to perform.
+         * @param args Optional arguments to supply to the
+         *            {@link MediaSessionCompat} for this custom action.
+         */
+        public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
+                Bundle args);
+
+        /**
+         * Send the id and args from a custom action for the
+         * {@link MediaSessionCompat} to perform.
+         *
+         * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
+         *      Bundle args)
+         * @param action The action identifier of the
+         *            {@link PlaybackStateCompat.CustomAction} as specified by
+         *            the {@link MediaSessionCompat}.
+         * @param args Optional arguments to supply to the
+         *            {@link MediaSessionCompat} for this custom action.
+         */
+        public abstract void sendCustomAction(String action, Bundle args);
     }
 
     /**
@@ -406,6 +721,7 @@
          * @return The stream this session is playing on.
          */
         public int getAudioStream() {
+            // TODO switch to AudioAttributesCompat when it is added.
             return mAudioStream;
         }
 
@@ -451,54 +767,215 @@
         TransportControls getTransportControls();
         PlaybackStateCompat getPlaybackState();
         MediaMetadataCompat getMetadata();
+
+        List<MediaSessionCompat.QueueItem> getQueue();
+        CharSequence getQueueTitle();
+        Bundle getExtras();
         int getRatingType();
+        long getFlags();
         PlaybackInfo getPlaybackInfo();
+        PendingIntent getSessionActivity();
+
+        void setVolumeTo(int value, int flags);
+        void adjustVolume(int direction, int flags);
         void sendCommand(String command, Bundle params, ResultReceiver cb);
+
+        String getPackageName();
         Object getMediaController();
     }
 
-    // TODO: compatibility implementation
     static class MediaControllerImplBase implements MediaControllerImpl {
+        private MediaSessionCompat.Token mToken;
+        private IMediaSession mBinder;
+        private TransportControls mTransportControls;
+
+        public MediaControllerImplBase(MediaSessionCompat.Token token) {
+            mToken = token;
+            mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
+        }
+
         @Override
         public void registerCallback(Callback callback, Handler handler) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback may not be null.");
+            }
+            try {
+                mBinder.asBinder().linkToDeath(callback, 0);
+                mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
+                callback.setHandler(handler);
+                callback.mRegistered = true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in registerCallback. " + e);
+                callback.onSessionDestroyed();
+            }
         }
 
         @Override
         public void unregisterCallback(Callback callback) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback may not be null.");
+            }
+            try {
+                mBinder.unregisterCallbackListener(
+                        (IMediaControllerCallback) callback.mCallbackObj);
+                mBinder.asBinder().unlinkToDeath(callback, 0);
+                callback.mRegistered = false;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in unregisterCallback. " + e);
+            }
         }
 
         @Override
         public boolean dispatchMediaButtonEvent(KeyEvent event) {
+            if (event == null) {
+                throw new IllegalArgumentException("event may not be null.");
+            }
+            try {
+                mBinder.sendMediaButton(event);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e);
+            }
             return false;
         }
 
         @Override
         public TransportControls getTransportControls() {
-            return null;
+            if (mTransportControls == null) {
+                mTransportControls = new TransportControlsBase(mBinder);
+            }
+
+            return mTransportControls;
         }
 
         @Override
         public PlaybackStateCompat getPlaybackState() {
+            try {
+                return mBinder.getPlaybackState();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPlaybackState. " + e);
+            }
             return null;
         }
 
         @Override
         public MediaMetadataCompat getMetadata() {
+            try {
+                return mBinder.getMetadata();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getMetadata. " + e);
+            }
+            return null;
+        }
+
+        @Override
+        public List<MediaSessionCompat.QueueItem> getQueue() {
+            try {
+                return mBinder.getQueue();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getQueue. " + e);
+            }
+            return null;
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            try {
+                return mBinder.getQueueTitle();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getQueueTitle. " + e);
+            }
+            return null;
+        }
+
+        @Override
+        public Bundle getExtras() {
+            try {
+                return mBinder.getExtras();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getExtras. " + e);
+            }
             return null;
         }
 
         @Override
         public int getRatingType() {
+            try {
+                return mBinder.getRatingType();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getRatingType. " + e);
+            }
+            return 0;
+        }
+
+        @Override
+        public long getFlags() {
+            try {
+                return mBinder.getFlags();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getFlags. " + e);
+            }
             return 0;
         }
 
         @Override
         public PlaybackInfo getPlaybackInfo() {
+            try {
+                ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
+                PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
+                        info.controlType, info.maxVolume, info.currentVolume);
+                return pi;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPlaybackInfo. " + e);
+            }
             return null;
         }
 
         @Override
+        public PendingIntent getSessionActivity() {
+            try {
+                return mBinder.getLaunchPendingIntent();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getSessionActivity. " + e);
+            }
+            return null;
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            try {
+                mBinder.setVolumeTo(value, flags, null);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setVolumeTo. " + e);
+            }
+        }
+
+        @Override
+        public void adjustVolume(int direction, int flags) {
+            try {
+                mBinder.adjustVolume(direction, flags, null);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in adjustVolume. " + e);
+            }
+        }
+
+        @Override
         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+            try {
+                mBinder.sendCommand(command, params,
+                        new MediaSessionCompat.ResultReceiverWrapper(cb));
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in sendCommand. " + e);
+            }
+        }
+
+        @Override
+        public String getPackageName() {
+            try {
+                return mBinder.getPackageName();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in getPackageName. " + e);
+            }
+            return null;
         }
 
         @Override
@@ -507,6 +984,136 @@
         }
     }
 
+    static class TransportControlsBase extends TransportControls {
+        private IMediaSession mBinder;
+
+        public TransportControlsBase(IMediaSession binder) {
+            mBinder = binder;
+        }
+
+        @Override
+        public void play() {
+            try {
+                mBinder.play();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in play. " + e);
+            }
+        }
+
+        @Override
+        public void playFromMediaId(String mediaId, Bundle extras) {
+            try {
+                mBinder.playFromMediaId(mediaId, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in playFromMediaId. " + e);
+            }
+        }
+
+        @Override
+        public void playFromSearch(String query, Bundle extras) {
+            try {
+                mBinder.playFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in playFromSearch. " + e);
+            }
+        }
+
+        @Override
+        public void skipToQueueItem(long id) {
+            try {
+                mBinder.skipToQueueItem(id);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToQueueItem. " + e);
+            }
+        }
+
+        @Override
+        public void pause() {
+            try {
+                mBinder.pause();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in pause. " + e);
+            }
+        }
+
+        @Override
+        public void stop() {
+            try {
+                mBinder.stop();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in stop. " + e);
+            }
+        }
+
+        @Override
+        public void seekTo(long pos) {
+            try {
+                mBinder.seekTo(pos);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in seekTo. " + e);
+            }
+        }
+
+        @Override
+        public void fastForward() {
+            try {
+                mBinder.fastForward();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in fastForward. " + e);
+            }
+        }
+
+        @Override
+        public void skipToNext() {
+            try {
+                mBinder.next();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToNext. " + e);
+            }
+        }
+
+        @Override
+        public void rewind() {
+            try {
+                mBinder.rewind();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in rewind. " + e);
+            }
+        }
+
+        @Override
+        public void skipToPrevious() {
+            try {
+                mBinder.previous();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in skipToPrevious. " + e);
+            }
+        }
+
+        @Override
+        public void setRating(RatingCompat rating) {
+            try {
+                mBinder.rate(rating);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in setRating. " + e);
+            }
+        }
+
+        @Override
+        public void sendCustomAction(CustomAction customAction, Bundle args) {
+            sendCustomAction(customAction.getAction(), args);
+        }
+
+        @Override
+        public void sendCustomAction(String action, Bundle args) {
+            try {
+                mBinder.sendCustomAction(action, args);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in sendCustomAction. " + e);
+            }
+        }
+    }
+
     static class MediaControllerImplApi21 implements MediaControllerImpl {
         private final Object mControllerObj;
 
@@ -517,7 +1124,6 @@
 
         public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
                 throws RemoteException {
-            // TODO: refactor framework implementation
             mControllerObj = MediaControllerCompatApi21.fromToken(context,
                     sessionToken.getToken());
             if (mControllerObj == null) throw new RemoteException();
@@ -557,11 +1163,40 @@
         }
 
         @Override
+        public List<MediaSessionCompat.QueueItem> getQueue() {
+            List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
+            if (queueObjs == null) {
+                return null;
+            }
+            List<MediaSessionCompat.QueueItem> queue =
+                    new ArrayList<MediaSessionCompat.QueueItem>();
+            for (Object item : queueObjs) {
+                queue.add(MediaSessionCompat.QueueItem.obtain(item));
+            }
+            return queue;
+        }
+
+        @Override
+        public CharSequence getQueueTitle() {
+            return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
+        }
+
+        @Override
+        public Bundle getExtras() {
+            return MediaControllerCompatApi21.getExtras(mControllerObj);
+        }
+
+        @Override
         public int getRatingType() {
             return MediaControllerCompatApi21.getRatingType(mControllerObj);
         }
 
         @Override
+        public long getFlags() {
+            return MediaControllerCompatApi21.getFlags(mControllerObj);
+        }
+
+        @Override
         public PlaybackInfo getPlaybackInfo() {
             Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
             return volumeInfoObj != null ? new PlaybackInfo(
@@ -573,11 +1208,31 @@
         }
 
         @Override
+        public PendingIntent getSessionActivity() {
+            return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
+        }
+
+        @Override
+        public void setVolumeTo(int value, int flags) {
+            MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
+        }
+
+        @Override
+        public void adjustVolume(int direction, int flags) {
+            MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
+        }
+
+        @Override
         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
             MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
         }
 
         @Override
+        public String getPackageName() {
+            return MediaControllerCompatApi21.getPackageName(mControllerObj);
+        }
+
+        @Override
         public Object getMediaController() {
             return mControllerObj;
         }
@@ -635,5 +1290,34 @@
             MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
                     rating != null ? rating.getRating() : null);
         }
+
+        @Override
+        public void playFromMediaId(String mediaId, Bundle extras) {
+            MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
+                    extras);
+        }
+
+        @Override
+        public void playFromSearch(String query, Bundle extras) {
+            MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
+                    extras);
+        }
+
+        @Override
+        public void skipToQueueItem(long id) {
+            MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
+        }
+
+        @Override
+        public void sendCustomAction(CustomAction customAction, Bundle args) {
+            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
+                    customAction.getAction(), args);
+        }
+
+        @Override
+        public void sendCustomAction(String action, Bundle args) {
+            MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
+                    args);
+        }
     }
 }
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl b/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl
new file mode 100644
index 0000000..d0c2f6f
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.aidl
@@ -0,0 +1,20 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable MediaSessionCompat.Token;
+parcelable MediaSessionCompat.QueueItem;
+parcelable MediaSessionCompat.ResultReceiverWrapper;
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
index e92b09a..8f11ef4 100644
--- a/v4/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -17,18 +17,34 @@
 
 package android.support.v4.media.session;
 
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.v4.media.MediaDescriptionCompat;
 import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.RatingCompat;
 import android.support.v4.media.VolumeProviderCompat;
 import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Allows interaction with media controllers, volume keys, media buttons, and
@@ -49,7 +65,8 @@
  * When an app is finished performing playback it must call {@link #release()}
  * to clean up the session and notify any controllers.
  * <p>
- * MediaSession objects are thread safe.
+ * MediaSessionCompat objects are not thread safe and all calls should be made
+ * from the same thread.
  * <p>
  * This is a helper for accessing features in
  * {@link android.media.session.MediaSession} introduced after API level 4 in a
@@ -57,6 +74,9 @@
  */
 public class MediaSessionCompat {
     private final MediaSessionImpl mImpl;
+    private final MediaControllerCompat mController;
+    private final ArrayList<OnActiveChangeListener>
+            mActiveListeners = new ArrayList<OnActiveChangeListener>();
 
     /**
      * Set this flag on the session to indicate that it can handle media button
@@ -75,8 +95,16 @@
      *
      * @param context The context.
      * @param tag A short name for debugging purposes.
+     * @param mediaButtonEventReceiver The component name for your receiver.
+     *            This must be non-null to support platform versions earlier
+     *            than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+     * @param mbrIntent The PendingIntent for your receiver component that
+     *            handles media button events. This is optional and will be used
+     *            on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
+     *            later instead of the component name.
      */
-    public MediaSessionCompat(Context context, String tag) {
+    public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver,
+            PendingIntent mbrIntent) {
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
@@ -86,13 +114,16 @@
 
         if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaSessionImplApi21(context, tag);
+            mImpl.setMediaButtonReceiver(mbrIntent);
         } else {
-            mImpl = new MediaSessionImplBase();
+            mImpl = new MediaSessionImplBase(context, tag, mediaButtonEventReceiver, mbrIntent);
         }
+        mController = new MediaControllerCompat(context, this);
     }
 
-    private MediaSessionCompat(MediaSessionImpl impl) {
+    private MediaSessionCompat(Context context, MediaSessionImpl impl) {
         mImpl = impl;
+        mController = new MediaControllerCompat(context, this);
     }
 
     /**
@@ -119,6 +150,35 @@
     }
 
     /**
+     * Set an intent for launching UI for this Session. This can be used as a
+     * quick link to an ongoing media screen. The intent should be for an
+     * activity that may be started using
+     * {@link Activity#startActivity(Intent)}.
+     *
+     * @param pi The intent to launch to show UI for this Session.
+     */
+    public void setSessionActivity(PendingIntent pi) {
+        mImpl.setSessionActivity(pi);
+    }
+
+    /**
+     * Set a pending intent for your media button receiver to allow restarting
+     * playback after the session has been stopped. If your app is started in
+     * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
+     * the pending intent.
+     * <p>
+     * This method will only work on
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. Earlier
+     * platform versions must include the media button receiver in the
+     * constructor.
+     *
+     * @param mbr The {@link PendingIntent} to send the media button event to.
+     */
+    public void setMediaButtonReceiver(PendingIntent mbr) {
+        mImpl.setMediaButtonReceiver(mbr);
+    }
+
+    /**
      * Set any flags for the session.
      *
      * @param flags The flags to set for this session.
@@ -147,6 +207,11 @@
      * current stream volume for this session. If {@link #setPlaybackToLocal}
      * was previously called that stream will stop receiving volume changes for
      * this session.
+     * <p>
+     * On platforms earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}
+     * this will only allow an app to handle volume commands sent directly to
+     * the session by a {@link MediaControllerCompat}. System routing of volume
+     * keys will not use the volume provider.
      *
      * @param volumeProvider The provider that will handle volume changes. May
      *            not be null.
@@ -163,11 +228,19 @@
      * set to false your session's controller may not be discoverable. You must
      * set the session to active before it can start receiving media button
      * events or transport commands.
+     * <p>
+     * On platforms earlier than
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP},
+     * {@link #setMediaButtonReceiver(PendingIntent)} must be called before
+     * setting this to true.
      *
      * @param active Whether this session is active or not.
      */
     public void setActive(boolean active) {
         mImpl.setActive(active);
+        for (OnActiveChangeListener listener : mActiveListeners) {
+            listener.onActiveChanged();
+        }
     }
 
     /**
@@ -205,10 +278,16 @@
 
     /**
      * Retrieve a token object that can be used by apps to create a
-     * {@link MediaControllerCompat} for interacting with this session. The owner of
-     * the session is responsible for deciding how to distribute these tokens.
+     * {@link MediaControllerCompat} for interacting with this session. The
+     * owner of the session is responsible for deciding how to distribute these
+     * tokens.
+     * <p>
+     * On platform versions before
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP} this token may only be
+     * used within your app as there is no way to guarantee other apps are using
+     * the same version of the support library.
      *
-     * @return A token that can be used to create a MediaController for this
+     * @return A token that can be used to create a media controller for this
      *         session.
      */
     public Token getSessionToken() {
@@ -216,6 +295,16 @@
     }
 
     /**
+     * Get a controller for this session. This is a convenience method to avoid
+     * having to cache your own controller in process.
+     *
+     * @return A controller for this session.
+     */
+    public MediaControllerCompat getController() {
+        return mController;
+    }
+
+    /**
      * Update the current playback state.
      *
      * @param state The current state of playback
@@ -235,27 +324,124 @@
     }
 
     /**
-     * Gets the underlying framework {@link android.media.session.MediaSession} object.
+     * Update the list of items in the play queue. It is an ordered list and
+     * should contain the current item, and previous or upcoming items if they
+     * exist. Specify null if there is no current play queue.
+     * <p>
+     * The queue should be of reasonable size. If the play queue is unbounded
+     * within your app, it is better to send a reasonable amount in a sliding
+     * window instead.
+     *
+     * @param queue A list of items in the play queue.
+     */
+    public void setQueue(List<QueueItem> queue) {
+        mImpl.setQueue(queue);
+    }
+
+    /**
+     * Set the title of the play queue. The UI should display this title along
+     * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
+     * name.
+     *
+     * @param title The title of the play queue.
+     */
+    public void setQueueTitle(CharSequence title) {
+        mImpl.setQueueTitle(title);
+    }
+
+    /**
+     * Set the style of rating used by this session. Apps trying to set the
+     * rating should use this style. Must be one of the following:
+     * <ul>
+     * <li>{@link RatingCompat#RATING_NONE}</li>
+     * <li>{@link RatingCompat#RATING_3_STARS}</li>
+     * <li>{@link RatingCompat#RATING_4_STARS}</li>
+     * <li>{@link RatingCompat#RATING_5_STARS}</li>
+     * <li>{@link RatingCompat#RATING_HEART}</li>
+     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
+     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
+     * </ul>
+     */
+    public void setRatingType(int type) {
+        mImpl.setRatingType(type);
+    }
+
+    /**
+     * Set some extras that can be associated with the
+     * {@link MediaSessionCompat}. No assumptions should be made as to how a
+     * {@link MediaControllerCompat} will handle these extras. Keys should be
+     * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
+     *
+     * @param extras The extras associated with the session.
+     */
+    public void setExtras(Bundle extras) {
+        mImpl.setExtras(extras);
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.session.MediaSession}
+     * object.
      * <p>
      * This method is only supported on API 21+.
      * </p>
      *
      * @return The underlying {@link android.media.session.MediaSession} object,
-     * or null if none.
+     *         or null if none.
      */
     public Object getMediaSession() {
         return mImpl.getMediaSession();
     }
 
     /**
+     * Gets the underlying framework {@link android.media.RemoteControlClient}
+     * object.
+     * <p>
+     * This method is only supported on APIs 14-20. On API 21+
+     * {@link #getMediaSession()} should be used instead.
+     *
+     * @return The underlying {@link android.media.RemoteControlClient} object,
+     *         or null if none.
+     */
+    public Object getRemoteControlClient() {
+        return mImpl.getRemoteControlClient();
+    }
+
+    /**
+     * Adds a listener to be notified when the active status of this session
+     * changes. This is primarily used by the support library and should not be
+     * needed by apps.
+     *
+     * @param listener The listener to add.
+     */
+    public void addOnActiveChangeListener(OnActiveChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener may not be null");
+        }
+        mActiveListeners.add(listener);
+    }
+
+    /**
+     * Stops the listener from being notified when the active status of this
+     * session changes.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeOnActiveChangeListener(OnActiveChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("Listener may not be null");
+        }
+        mActiveListeners.remove(listener);
+    }
+
+    /**
      * Obtain a compat wrapper for an existing MediaSession.
      *
      * @param mediaSession The {@link android.media.session.MediaSession} to
      *            wrap.
      * @return A compat wrapper for the provided session.
      */
-    public static MediaSessionCompat obtain(Object mediaSession) {
-        return new MediaSessionCompat(new MediaSessionImplApi21(mediaSession));
+    public static MediaSessionCompat obtain(Context context, Object mediaSession) {
+        return new MediaSessionCompat(context, new MediaSessionImplApi21(mediaSession));
     }
 
     /**
@@ -302,6 +488,29 @@
         }
 
         /**
+         * Override to handle requests to play a specific mediaId that was
+         * provided by your app.
+         */
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to begin playback from a search query. An
+         * empty query indicates that the app may play any music. The
+         * implementation should attempt to make a smart choice about what to
+         * play.
+         */
+        public void onPlayFromSearch(String query, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to play an item with a given id from the
+         * play queue.
+         */
+        public void onSkipToQueueItem(long id) {
+        }
+
+        /**
          * Override to handle requests to pause playback.
          */
         public void onPause() {
@@ -353,6 +562,18 @@
         public void onSetRating(RatingCompat rating) {
         }
 
+        /**
+         * Called when a {@link MediaControllerCompat} wants a
+         * {@link PlaybackStateCompat.CustomAction} to be performed.
+         *
+         * @param action The action that was originally sent in the
+         *            {@link PlaybackStateCompat.CustomAction}.
+         * @param extras Optional extras specified by the
+         *            {@link MediaControllerCompat}.
+         */
+        public void onCustomAction(String action, Bundle extras) {
+        }
+
         private class StubApi21 implements MediaSessionCompatApi21.Callback {
 
             @Override
@@ -371,6 +592,21 @@
             }
 
             @Override
+            public void onPlayFromMediaId(String mediaId, Bundle extras) {
+                Callback.this.onPlayFromMediaId(mediaId, extras);
+            }
+
+            @Override
+            public void onPlayFromSearch(String search, Bundle extras) {
+                Callback.this.onPlayFromSearch(search, extras);
+            }
+
+            @Override
+            public void onSkipToQueueItem(long id) {
+                Callback.this.onSkipToQueueItem(id);
+            }
+
+            @Override
             public void onPause() {
                 Callback.this.onPause();
             }
@@ -409,6 +645,11 @@
             public void onSetRating(Object ratingObj) {
                 Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
             }
+
+            @Override
+            public void onCustomAction(String action, Bundle extras) {
+                Callback.this.onCustomAction(action, extras);
+            }
         }
     }
 
@@ -418,20 +659,42 @@
      * the session.
      */
     public static final class Token implements Parcelable {
-        private final Parcelable mInner;
+        private final Object mInner;
 
-        Token(Parcelable inner) {
+        Token(Object inner) {
             mInner = inner;
         }
 
+        /**
+         * Creates a compat Token from a framework
+         * {@link android.media.session.MediaSession.Token} object.
+         * <p>
+         * This method is only supported on
+         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
+         * </p>
+         *
+         * @param token The framework token object.
+         * @return A compat Token for use with {@link MediaControllerCompat}.
+         */
+        public static Token fromToken(Object token) {
+            if (token == null || android.os.Build.VERSION.SDK_INT < 21) {
+                return null;
+            }
+            return new Token(MediaSessionCompatApi21.verifyToken(token));
+        }
+
         @Override
         public int describeContents() {
-            return mInner.describeContents();
+            return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(mInner, flags);
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                dest.writeParcelable((Parcelable) mInner, flags);
+            } else {
+                dest.writeStrongBinder((IBinder) mInner);
+            }
         }
 
         /**
@@ -451,7 +714,13 @@
                 = new Parcelable.Creator<Token>() {
             @Override
             public Token createFromParcel(Parcel in) {
-                return new Token(in.readParcelable(null));
+                Object inner;
+                if (android.os.Build.VERSION.SDK_INT >= 21) {
+                    inner = in.readParcelable(null);
+                } else {
+                    inner = in.readStrongBinder();
+                }
+                return new Token(inner);
             }
 
             @Override
@@ -461,6 +730,174 @@
         };
     }
 
+    /**
+     * A single item that is part of the play queue. It contains a description
+     * of the item and its id in the queue.
+     */
+    public static final class QueueItem implements Parcelable {
+        /**
+         * This id is reserved. No items can be explicitly asigned this id.
+         */
+        public static final int UNKNOWN_ID = -1;
+
+        private final MediaDescriptionCompat mDescription;
+        private final long mId;
+
+        private Object mItem;
+
+        /**
+         * Create a new {@link MediaSessionCompat.QueueItem}.
+         *
+         * @param description The {@link MediaDescriptionCompat} for this item.
+         * @param id An identifier for this item. It must be unique within the
+         *            play queue and cannot be {@link #UNKNOWN_ID}.
+         */
+        public QueueItem(MediaDescriptionCompat description, long id) {
+            this(null, description, id);
+        }
+
+        private QueueItem(Object queueItem, MediaDescriptionCompat description, long id) {
+            if (description == null) {
+                throw new IllegalArgumentException("Description cannot be null.");
+            }
+            if (id == UNKNOWN_ID) {
+                throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
+            }
+            mDescription = description;
+            mId = id;
+            mItem = queueItem;
+        }
+
+        private QueueItem(Parcel in) {
+            mDescription = MediaDescriptionCompat.CREATOR.createFromParcel(in);
+            mId = in.readLong();
+        }
+
+        /**
+         * Get the description for this item.
+         */
+        public MediaDescriptionCompat getDescription() {
+            return mDescription;
+        }
+
+        /**
+         * Get the queue id for this item.
+         */
+        public long getQueueId() {
+            return mId;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mDescription.writeToParcel(dest, flags);
+            dest.writeLong(mId);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /**
+         * Get the underlying
+         * {@link android.media.session.MediaSession.QueueItem}.
+         * <p>
+         * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
+         * is returned.
+         *
+         * @return The underlying
+         *         {@link android.media.session.MediaSession.QueueItem} or null.
+         */
+        public Object getQueueItem() {
+            if (mItem != null || android.os.Build.VERSION.SDK_INT < 21) {
+                return mItem;
+            }
+            mItem = MediaSessionCompatApi21.QueueItem.createItem(mDescription.getMediaDescription(),
+                    mId);
+            return mItem;
+        }
+
+        /**
+         * Obtain a compat wrapper for an existing QueueItem.
+         *
+         * @param queueItem The {@link android.media.session.MediaSession.QueueItem} to
+         *            wrap.
+         * @return A compat wrapper for the provided item.
+         */
+        public static QueueItem obtain(Object queueItem) {
+            Object descriptionObj = MediaSessionCompatApi21.QueueItem.getDescription(queueItem);
+            MediaDescriptionCompat description = MediaDescriptionCompat.fromMediaDescription(
+                    descriptionObj);
+            long id = MediaSessionCompatApi21.QueueItem.getQueueId(queueItem);
+            return new QueueItem(queueItem, description, id);
+        }
+
+        public static final Creator<MediaSessionCompat.QueueItem>
+                CREATOR = new Creator<MediaSessionCompat.QueueItem>() {
+
+                        @Override
+                    public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
+                        return new MediaSessionCompat.QueueItem(p);
+                    }
+
+                        @Override
+                    public MediaSessionCompat.QueueItem[] newArray(int size) {
+                        return new MediaSessionCompat.QueueItem[size];
+                    }
+                };
+
+        @Override
+        public String toString() {
+            return "MediaSession.QueueItem {" +
+                    "Description=" + mDescription +
+                    ", Id=" + mId + " }";
+        }
+    }
+
+    /**
+     * This is a wrapper for {@link ResultReceiver} for sending over aidl
+     * interfaces. The framework version was not exposed to aidls until
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+     */
+    static final class ResultReceiverWrapper implements Parcelable {
+        private ResultReceiver mResultReceiver;
+
+        public ResultReceiverWrapper(ResultReceiver resultReceiver) {
+            mResultReceiver = resultReceiver;
+        }
+
+        ResultReceiverWrapper(Parcel in) {
+            mResultReceiver = ResultReceiver.CREATOR.createFromParcel(in);
+        }
+
+        public static final Creator<ResultReceiverWrapper>
+                CREATOR = new Creator<ResultReceiverWrapper>() {
+            @Override
+            public ResultReceiverWrapper createFromParcel(Parcel p) {
+                return new ResultReceiverWrapper(p);
+            }
+
+            @Override
+            public ResultReceiverWrapper[] newArray(int size) {
+                return new ResultReceiverWrapper[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mResultReceiver.writeToParcel(dest, flags);
+        }
+    }
+
+    public interface OnActiveChangeListener {
+        void onActiveChanged();
+    }
+
     interface MediaSessionImpl {
         void setCallback(Callback callback, Handler handler);
         void setFlags(int flags);
@@ -473,67 +910,888 @@
         Token getSessionToken();
         void setPlaybackState(PlaybackStateCompat state);
         void setMetadata(MediaMetadataCompat metadata);
+
+        void setSessionActivity(PendingIntent pi);
+
+        void setMediaButtonReceiver(PendingIntent mbr);
+        void setQueue(List<QueueItem> queue);
+        void setQueueTitle(CharSequence title);
+
+        void setRatingType(int type);
+        void setExtras(Bundle extras);
+
         Object getMediaSession();
+
+        Object getRemoteControlClient();
     }
 
     // TODO: compatibility implementation
     static class MediaSessionImplBase implements MediaSessionImpl {
+        private final Context mContext;
+        private final ComponentName mComponentName;
+        private final PendingIntent mMediaButtonEventReceiver;
+        private final Object mRccObj;
+        private final MediaSessionStub mStub;
+        private final Token mToken;
+        private final MessageHandler mHandler;
+        private final String mPackageName;
+        private final String mTag;
+        private final AudioManager mAudioManager;
+
+        private final Object mLock = new Object();
+        private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
+                = new RemoteCallbackList<IMediaControllerCallback>();
+
+        private boolean mDestroyed = false;
+        private boolean mIsActive = false;
+        private boolean mIsRccRegistered = false;
+        private boolean mIsMbrRegistered = false;
+        private Callback mCallback;
+
+        private int mFlags;
+
+        private MediaMetadataCompat mMetadata;
+        private PlaybackStateCompat mState;
+        private PendingIntent mSessionActivity;
+        private List<QueueItem> mQueue;
+        private CharSequence mQueueTitle;
+        private int mRatingType;
+        private Bundle mExtras;
+
+        private int mVolumeType;
+        private int mLocalStream;
+        private VolumeProviderCompat mVolumeProvider;
+
+        private VolumeProviderCompat.Callback mVolumeCallback
+                = new VolumeProviderCompat.Callback() {
+            @Override
+            public void onVolumeChanged(VolumeProviderCompat volumeProvider) {
+                if (mVolumeProvider != volumeProvider) {
+                    return;
+                }
+                ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+                        volumeProvider.getVolumeControl(), volumeProvider.getMaxVolume(),
+                        volumeProvider.getCurrentVolume());
+                sendVolumeInfoChanged(info);
+            }
+        };
+
+        public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
+                PendingIntent mbr) {
+            if (mbrComponent == null) {
+                throw new IllegalArgumentException(
+                        "MediaButtonReceiver component may not be null.");
+            }
+            if (mbr == null) {
+                // construct a PendingIntent for the media button
+                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+                // the associated intent will be handled by the component being
+                // registered
+                mediaButtonIntent.setComponent(mbrComponent);
+                mbr = PendingIntent.getBroadcast(context,
+                        0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
+            }
+            mContext = context;
+            mPackageName = context.getPackageName();
+            mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+            mTag = tag;
+            mComponentName = mbrComponent;
+            mMediaButtonEventReceiver = mbr;
+            mStub = new MediaSessionStub();
+            mToken = new Token(mStub);
+            mHandler = new MessageHandler(Looper.myLooper());
+
+            mRatingType = RatingCompat.RATING_NONE;
+            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+            mLocalStream = AudioManager.STREAM_MUSIC;
+            if (android.os.Build.VERSION.SDK_INT >= 14) {
+                mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr);
+            } else {
+                mRccObj = null;
+            }
+        }
+
         @Override
-        public void setCallback(Callback callback, Handler handler) {
+        public void setCallback(final Callback callback, Handler handler) {
+            if (callback == mCallback) {
+                return;
+            }
+            if (callback == null || android.os.Build.VERSION.SDK_INT < 18) {
+                // There's nothing to register on API < 18 since media buttons
+                // all go through the media button receiver
+                if (android.os.Build.VERSION.SDK_INT >= 18) {
+                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
+                }
+                if (android.os.Build.VERSION.SDK_INT >= 19) {
+                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj, null);
+                }
+            } else {
+                if (handler == null) {
+                    handler = new Handler();
+                }
+                MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() {
+                    @Override
+                    public void onStop() {
+                        callback.onStop();
+                    }
+
+                    @Override
+                    public void onSkipToPrevious() {
+                        callback.onSkipToPrevious();
+                    }
+
+                    @Override
+                    public void onSkipToNext() {
+                        callback.onSkipToNext();
+                    }
+
+                    @Override
+                    public void onSetRating(Object ratingObj) {
+                        callback.onSetRating(RatingCompat.fromRating(ratingObj));
+                    }
+
+                    @Override
+                    public void onSeekTo(long pos) {
+                        callback.onSeekTo(pos);
+                    }
+
+                    @Override
+                    public void onRewind() {
+                        callback.onRewind();
+                    }
+
+                    @Override
+                    public void onPlay() {
+                        callback.onPlay();
+                    }
+
+                    @Override
+                    public void onPause() {
+                        callback.onPause();
+                    }
+
+                    @Override
+                    public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                        return callback.onMediaButtonEvent(mediaButtonIntent);
+                    }
+
+                    @Override
+                    public void onFastForward() {
+                        callback.onFastForward();
+                    }
+
+                    @Override
+                    public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+                        callback.onCommand(command, extras, cb);
+                    }
+                };
+                if (android.os.Build.VERSION.SDK_INT >= 18) {
+                    Object onPositionUpdateObj = MediaSessionCompatApi18
+                            .createPlaybackPositionUpdateListener(cb14);
+                    MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
+                            onPositionUpdateObj);
+                }
+                if (android.os.Build.VERSION.SDK_INT >= 19) {
+                    Object onMetadataUpdateObj = MediaSessionCompatApi19
+                            .createMetadataUpdateListener(cb14);
+                    MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
+                            onMetadataUpdateObj);
+                }
+            }
+            mCallback = callback;
         }
 
         @Override
         public void setFlags(int flags) {
+            synchronized (mLock) {
+                mFlags = flags;
+            }
+            update();
         }
 
         @Override
         public void setPlaybackToLocal(int stream) {
+            if (mVolumeProvider != null) {
+                mVolumeProvider.setCallback(null);
+            }
+            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+                    VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE,
+                    mAudioManager.getStreamMaxVolume(mLocalStream),
+                    mAudioManager.getStreamVolume(mLocalStream));
+            sendVolumeInfoChanged(info);
         }
 
         @Override
         public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
+            if (volumeProvider == null) {
+                throw new IllegalArgumentException("volumeProvider may not be null");
+            }
+            if (mVolumeProvider != null) {
+                mVolumeProvider.setCallback(null);
+            }
+            mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+            mVolumeProvider = volumeProvider;
+            ParcelableVolumeInfo info = new ParcelableVolumeInfo(mVolumeType, mLocalStream,
+                    mVolumeProvider.getVolumeControl(), mVolumeProvider.getMaxVolume(),
+                    mVolumeProvider.getCurrentVolume());
+            sendVolumeInfoChanged(info);
+
+            volumeProvider.setCallback(mVolumeCallback);
         }
 
         @Override
         public void setActive(boolean active) {
+            if (active == mIsActive) {
+                return;
+            }
+            mIsActive = active;
+            if (update()) {
+                setMetadata(mMetadata);
+                setPlaybackState(mState);
+            }
         }
 
         @Override
         public boolean isActive() {
-            return false;
+            return mIsActive;
         }
 
         @Override
         public void sendSessionEvent(String event, Bundle extras) {
+            sendEvent(event, extras);
         }
 
         @Override
         public void release() {
+            mIsActive = false;
+            mDestroyed = true;
+            update();
+            sendSessionDestroyed();
         }
 
         @Override
         public Token getSessionToken() {
-            return null;
+            return mToken;
         }
 
         @Override
         public void setPlaybackState(PlaybackStateCompat state) {
+            synchronized (mLock) {
+                mState = state;
+            }
+            sendState(state);
+            if (!mIsActive) {
+                // Don't set the state until after the RCC is registered
+                return;
+            }
+            if (state == null) {
+                if (android.os.Build.VERSION.SDK_INT >= 14) {
+                    MediaSessionCompatApi14.setState(mRccObj, PlaybackStateCompat.STATE_NONE);
+                }
+            } else {
+                if (android.os.Build.VERSION.SDK_INT >= 18) {
+                    MediaSessionCompatApi18.setState(mRccObj, state.getState(), state.getPosition(),
+                            state.getPlaybackSpeed(), state.getLastPositionUpdateTime());
+                } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+                    MediaSessionCompatApi14.setState(mRccObj, state.getState());
+                }
+            }
         }
 
         @Override
         public void setMetadata(MediaMetadataCompat metadata) {
+            synchronized (mLock) {
+                mMetadata = metadata;
+            }
+            sendMetadata(metadata);
+            if (!mIsActive) {
+                // Don't set metadata until after the rcc has been registered
+                return;
+            }
+            if (android.os.Build.VERSION.SDK_INT >= 19) {
+                boolean canRate = mState != null
+                        && (mState.getActions() & PlaybackStateCompat.ACTION_SET_RATING) != 0;
+                MediaSessionCompatApi19.setMetadata(mRccObj,
+                        metadata == null ? null : metadata.getBundle(), canRate);
+            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+                MediaSessionCompatApi14.setMetadata(mRccObj,
+                        metadata == null ? null : metadata.getBundle());
+            }
+        }
+
+        @Override
+        public void setSessionActivity(PendingIntent pi) {
+            synchronized (mLock) {
+                mSessionActivity = pi;
+            }
+        }
+
+        @Override
+        public void setMediaButtonReceiver(PendingIntent mbr) {
+            // Do nothing, changing this is not supported before API 21.
+        }
+
+        @Override
+        public void setQueue(List<QueueItem> queue) {
+            mQueue = queue;
+            sendQueue(queue);
+        }
+
+        @Override
+        public void setQueueTitle(CharSequence title) {
+            mQueueTitle = title;
+            sendQueueTitle(title);
         }
 
         @Override
         public Object getMediaSession() {
             return null;
         }
+
+        @Override
+        public Object getRemoteControlClient() {
+            return mRccObj;
+        }
+
+        @Override
+        public void setRatingType(int type) {
+            mRatingType = type;
+        }
+
+        @Override
+        public void setExtras(Bundle extras) {
+            mExtras = extras;
+        }
+
+        // Registers/unregisters the RCC and MediaButtonEventReceiver as needed.
+        private boolean update() {
+            boolean registeredRcc = false;
+            if (mIsActive) {
+                // On API 8+ register a MBR if it's supported, unregister it
+                // if support was removed.
+                if (android.os.Build.VERSION.SDK_INT >= 8) {
+                    if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
+                        if (android.os.Build.VERSION.SDK_INT >= 18) {
+                            MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
+                                    mMediaButtonEventReceiver);
+                        } else {
+                            MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext,
+                                    mComponentName);
+                        }
+                        mIsMbrRegistered = true;
+                    } else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
+                        if (android.os.Build.VERSION.SDK_INT >= 18) {
+                            MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
+                                    mMediaButtonEventReceiver);
+                        } else {
+                            MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
+                                    mComponentName);
+                        }
+                        mIsMbrRegistered = false;
+                    }
+                }
+                // On API 14+ register a RCC if it's supported, unregister it if
+                // not.
+                if (android.os.Build.VERSION.SDK_INT >= 14) {
+                    if (!mIsRccRegistered && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0) {
+                        MediaSessionCompatApi14.registerRemoteControlClient(mContext, mRccObj);
+                        mIsRccRegistered = true;
+                        registeredRcc = true;
+                    } else if (mIsRccRegistered
+                            && (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) == 0) {
+                        MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
+                        mIsRccRegistered = false;
+                    }
+                }
+            } else {
+                // When inactive remove any registered components.
+                if (mIsMbrRegistered) {
+                    if (android.os.Build.VERSION.SDK_INT >= 18) {
+                        MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
+                                mMediaButtonEventReceiver);
+                    } else {
+                        MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
+                                mComponentName);
+                    }
+                    mIsMbrRegistered = false;
+                }
+                if (mIsRccRegistered) {
+                    MediaSessionCompatApi14.unregisterRemoteControlClient(mContext, mRccObj);
+                    mIsRccRegistered = false;
+                }
+            }
+            return registeredRcc;
+        }
+
+        private void adjustVolume(int direction, int flags) {
+            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                if (mVolumeProvider != null) {
+                    mVolumeProvider.onAdjustVolume(direction);
+                }
+            } else {
+                mAudioManager.adjustStreamVolume(direction, mLocalStream, flags);
+            }
+        }
+
+        private void setVolumeTo(int value, int flags) {
+            if (mVolumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                if (mVolumeProvider != null) {
+                    mVolumeProvider.onSetVolumeTo(value);
+                }
+            } else {
+                mAudioManager.setStreamVolume(mLocalStream, value, flags);
+            }
+        }
+
+        private PlaybackStateCompat getStateWithUpdatedPosition() {
+            PlaybackStateCompat state;
+            long duration = -1;
+            synchronized (mLock) {
+                state = mState;
+                if (mMetadata != null
+                        && mMetadata.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
+                    duration = mMetadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
+                }
+            }
+
+            PlaybackStateCompat result = null;
+            if (state != null) {
+                if (state.getState() == PlaybackStateCompat.STATE_PLAYING
+                        || state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING
+                        || state.getState() == PlaybackStateCompat.STATE_REWINDING) {
+                    long updateTime = state.getLastPositionUpdateTime();
+                    long currentTime = SystemClock.elapsedRealtime();
+                    if (updateTime > 0) {
+                        long position = (long) (state.getPlaybackSpeed()
+                                * (currentTime - updateTime)) + state.getPosition();
+                        if (duration >= 0 && position > duration) {
+                            position = duration;
+                        } else if (position < 0) {
+                            position = 0;
+                        }
+                        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(
+                                state);
+                        builder.setState(state.getState(), position, state.getPlaybackSpeed(),
+                                currentTime);
+                        result = builder.build();
+                    }
+                }
+            }
+            return result == null ? state : result;
+        }
+
+        private void sendVolumeInfoChanged(ParcelableVolumeInfo info) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onVolumeInfoChanged(info);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        private void sendSessionDestroyed() {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onSessionDestroyed();;
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+            mControllerCallbacks.kill();
+        }
+
+        private void sendEvent(String event, Bundle extras) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onEvent(event, extras);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        private void sendState(PlaybackStateCompat state) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onPlaybackStateChanged(state);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        private void sendMetadata(MediaMetadataCompat metadata) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onMetadataChanged(metadata);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        private void sendQueue(List<QueueItem> queue) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onQueueChanged(queue);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        private void sendQueueTitle(CharSequence queueTitle) {
+            int size = mControllerCallbacks.beginBroadcast();
+            for (int i = size - 1; i >= 0; i--) {
+                IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
+                try {
+                    cb.onQueueTitleChanged(queueTitle);
+                } catch (RemoteException e) {
+                }
+            }
+            mControllerCallbacks.finishBroadcast();
+        }
+
+        class MediaSessionStub extends IMediaSession.Stub {
+            @Override
+            public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
+                mHandler.post(MessageHandler.MSG_COMMAND,
+                        new Command(command, args, cb.mResultReceiver));
+            }
+
+            @Override
+            public boolean sendMediaButton(KeyEvent mediaButton) {
+                boolean handlesMediaButtons =
+                        (mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
+                if (handlesMediaButtons) {
+                    mHandler.post(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
+                }
+                return handlesMediaButtons;
+            }
+
+            @Override
+            public void registerCallbackListener(IMediaControllerCallback cb) {
+                // If this session is already destroyed tell the caller and
+                // don't add them.
+                if (mDestroyed) {
+                    try {
+                        cb.onSessionDestroyed();
+                    } catch (Exception e) {
+                        // ignored
+                    }
+                    return;
+                }
+                mControllerCallbacks.register(cb);
+            }
+
+            @Override
+            public void unregisterCallbackListener(IMediaControllerCallback cb) {
+                mControllerCallbacks.unregister(cb);
+            }
+
+            @Override
+            public String getPackageName() {
+                // mPackageName is final so doesn't need synchronize block
+                return mPackageName;
+            }
+
+            @Override
+            public String getTag() {
+                // mTag is final so doesn't need synchronize block
+                return mTag;
+            }
+
+            @Override
+            public PendingIntent getLaunchPendingIntent() {
+                synchronized (mLock) {
+                    return mSessionActivity;
+                }
+            }
+
+            @Override
+            public long getFlags() {
+                synchronized (mLock) {
+                    return mFlags;
+                }
+            }
+
+            @Override
+            public ParcelableVolumeInfo getVolumeAttributes() {
+                int controlType;
+                int max;
+                int current;
+                int stream;
+                int volumeType;
+                synchronized (mLock) {
+                    volumeType = mVolumeType;
+                    stream = mLocalStream;
+                    VolumeProviderCompat vp = mVolumeProvider;
+                    if (volumeType == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                        controlType = vp.getVolumeControl();
+                        max = vp.getMaxVolume();
+                        current = vp.getCurrentVolume();
+                    } else {
+                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+                        max = mAudioManager.getStreamMaxVolume(stream);
+                        current = mAudioManager.getStreamVolume(stream);
+                    }
+                }
+                return new ParcelableVolumeInfo(volumeType, stream, controlType, max, current);
+            }
+
+            @Override
+            public void adjustVolume(int direction, int flags, String packageName) {
+                MediaSessionImplBase.this.adjustVolume(direction, flags);
+            }
+
+            @Override
+            public void setVolumeTo(int value, int flags, String packageName) {
+                MediaSessionImplBase.this.setVolumeTo(value, flags);
+            }
+
+            @Override
+            public void play() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_PLAY);
+            }
+
+            @Override
+            public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+            }
+
+            @Override
+            public void playFromSearch(String query, Bundle extras) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_PLAY_SEARCH, query, extras);
+            }
+
+            @Override
+            public void skipToQueueItem(long id) {
+                mHandler.post(MessageHandler.MSG_SKIP_TO_ITEM, id);
+            }
+
+            @Override
+            public void pause() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_PAUSE);
+            }
+
+            @Override
+            public void stop() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_STOP);
+            }
+
+            @Override
+            public void next() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_NEXT);
+            }
+
+            @Override
+            public void previous() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_PREVIOUS);
+            }
+
+            @Override
+            public void fastForward() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_FAST_FORWARD);
+            }
+
+            @Override
+            public void rewind() throws RemoteException {
+                mHandler.post(MessageHandler.MSG_REWIND);
+            }
+
+            @Override
+            public void seekTo(long pos) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_SEEK_TO, pos);
+            }
+
+            @Override
+            public void rate(RatingCompat rating) throws RemoteException {
+                mHandler.post(MessageHandler.MSG_RATE, rating);
+            }
+
+            @Override
+            public void sendCustomAction(String action, Bundle args)
+                    throws RemoteException {
+                mHandler.post(MessageHandler.MSG_CUSTOM_ACTION, action, args);
+            }
+
+            @Override
+            public MediaMetadataCompat getMetadata() {
+                return mMetadata;
+            }
+
+            @Override
+            public PlaybackStateCompat getPlaybackState() {
+                return getStateWithUpdatedPosition();
+            }
+
+            @Override
+            public List<QueueItem> getQueue() {
+                synchronized (mLock) {
+                    return mQueue;
+                }
+            }
+
+            @Override
+            public CharSequence getQueueTitle() {
+                return mQueueTitle;
+            }
+
+            @Override
+            public Bundle getExtras() {
+                synchronized (mLock) {
+                    return mExtras;
+                }
+            }
+
+            @Override
+            public int getRatingType() {
+                return mRatingType;
+            }
+
+            @Override
+            public boolean isTransportControlEnabled() {
+                return (mFlags & FLAG_HANDLES_TRANSPORT_CONTROLS) != 0;
+            }
+        }
+
+        private static final class Command {
+            public final String command;
+            public final Bundle extras;
+            public final ResultReceiver stub;
+
+            public Command(String command, Bundle extras, ResultReceiver stub) {
+                this.command = command;
+                this.extras = extras;
+                this.stub = stub;
+            }
+        }
+
+        private class MessageHandler extends Handler {
+
+            private static final int MSG_PLAY = 1;
+            private static final int MSG_PLAY_MEDIA_ID = 2;
+            private static final int MSG_PLAY_SEARCH = 3;
+            private static final int MSG_SKIP_TO_ITEM = 4;
+            private static final int MSG_PAUSE = 5;
+            private static final int MSG_STOP = 6;
+            private static final int MSG_NEXT = 7;
+            private static final int MSG_PREVIOUS = 8;
+            private static final int MSG_FAST_FORWARD = 9;
+            private static final int MSG_REWIND = 10;
+            private static final int MSG_SEEK_TO = 11;
+            private static final int MSG_RATE = 12;
+            private static final int MSG_CUSTOM_ACTION = 13;
+            private static final int MSG_MEDIA_BUTTON = 14;
+            private static final int MSG_COMMAND = 15;
+            private static final int MSG_ADJUST_VOLUME = 16;
+            private static final int MSG_SET_VOLUME = 17;
+
+            public MessageHandler(Looper looper) {
+                super(looper);
+            }
+
+            public void post(int what, Object obj, Bundle bundle) {
+                Message msg = obtainMessage(what, obj);
+                msg.setData(bundle);
+                msg.sendToTarget();
+            }
+
+            public void post(int what, Object obj) {
+                obtainMessage(what, obj).sendToTarget();
+            }
+
+            public void post(int what) {
+                post(what, null);
+            }
+
+            public void post(int what, Object obj, int arg1) {
+                obtainMessage(what, arg1, 0, obj).sendToTarget();
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (mCallback == null) {
+                    return;
+                }
+                switch (msg.what) {
+                    case MSG_PLAY:
+                        mCallback.onPlay();
+                        break;
+                    case MSG_PLAY_MEDIA_ID:
+                        mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_PLAY_SEARCH:
+                        mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_SKIP_TO_ITEM:
+                        mCallback.onSkipToQueueItem((Long) msg.obj);
+                        break;
+                    case MSG_PAUSE:
+                        mCallback.onPause();
+                        break;
+                    case MSG_STOP:
+                        mCallback.onStop();
+                        break;
+                    case MSG_NEXT:
+                        mCallback.onSkipToNext();
+                        break;
+                    case MSG_PREVIOUS:
+                        mCallback.onSkipToPrevious();
+                        break;
+                    case MSG_FAST_FORWARD:
+                        mCallback.onFastForward();
+                        break;
+                    case MSG_REWIND:
+                        mCallback.onRewind();
+                        break;
+                    case MSG_SEEK_TO:
+                        mCallback.onSeekTo((Long) msg.obj);
+                        break;
+                    case MSG_RATE:
+                        mCallback.onSetRating((RatingCompat) msg.obj);
+                        break;
+                    case MSG_CUSTOM_ACTION:
+                        mCallback.onCustomAction((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_MEDIA_BUTTON:
+                        mCallback.onMediaButtonEvent((Intent) msg.obj);
+                        break;
+                    case MSG_COMMAND:
+                        Command cmd = (Command) msg.obj;
+                        mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+                        break;
+                    case MSG_ADJUST_VOLUME:
+                        adjustVolume((int) msg.obj, 0);
+                        break;
+                    case MSG_SET_VOLUME:
+                        setVolumeTo((int) msg.obj, 0);
+                        break;
+                }
+            }
+        }
     }
 
     static class MediaSessionImplApi21 implements MediaSessionImpl {
         private final Object mSessionObj;
         private final Token mToken;
 
+        private PendingIntent mMediaButtonIntent;
+
         public MediaSessionImplApi21(Context context, String tag) {
             mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
             mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
@@ -601,8 +1859,55 @@
         }
 
         @Override
+        public void setSessionActivity(PendingIntent pi) {
+            MediaSessionCompatApi21.setSessionActivity(mSessionObj, pi);
+        }
+
+        @Override
+        public void setMediaButtonReceiver(PendingIntent mbr) {
+            mMediaButtonIntent = mbr;
+            MediaSessionCompatApi21.setMediaButtonReceiver(mSessionObj, mbr);
+        }
+
+        @Override
+        public void setQueue(List<QueueItem> queue) {
+            List<Object> queueObjs = null;
+            if (queue != null) {
+                queueObjs = new ArrayList<Object>();
+                for (QueueItem item : queue) {
+                    queueObjs.add(item.getQueueItem());
+                }
+            }
+            MediaSessionCompatApi21.setQueue(mSessionObj, queueObjs);
+        }
+
+        @Override
+        public void setQueueTitle(CharSequence title) {
+            MediaSessionCompatApi21.setQueueTitle(mSessionObj, title);
+        }
+
+        @Override
+        public void setRatingType(int type) {
+            if (android.os.Build.VERSION.SDK_INT < 22) {
+                // TODO figure out 21 implementation
+            } else {
+                MediaSessionCompatApi22.setRatingType(mSessionObj, type);
+            }
+        }
+
+        @Override
+        public void setExtras(Bundle extras) {
+            MediaSessionCompatApi21.setExtras(mSessionObj, extras);
+        }
+
+        @Override
         public Object getMediaSession() {
             return mSessionObj;
         }
+
+        @Override
+        public Object getRemoteControlClient() {
+            return null;
+        }
     }
 }
diff --git a/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..2e77c4f
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable ParcelableVolumeInfo;
diff --git a/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java
new file mode 100644
index 0000000..678b33e
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/ParcelableVolumeInfo.java
@@ -0,0 +1,77 @@
+/* Copyright 2014, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Convenience class for passing information about the audio configuration of a
+ * {@link MediaSessionCompat}.
+ */
+public class ParcelableVolumeInfo implements Parcelable {
+    public int volumeType;
+    public int audioStream;
+    public int controlType;
+    public int maxVolume;
+    public int currentVolume;
+
+    public ParcelableVolumeInfo(int volumeType, int audioStream, int controlType,
+            int maxVolume,
+            int currentVolume) {
+        this.volumeType = volumeType;
+        this.audioStream = audioStream;
+        this.controlType = controlType;
+        this.maxVolume = maxVolume;
+        this.currentVolume = currentVolume;
+    }
+
+    public ParcelableVolumeInfo(Parcel from) {
+        volumeType = from.readInt();
+        controlType = from.readInt();
+        maxVolume = from.readInt();
+        currentVolume = from.readInt();
+        audioStream = from.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(volumeType);
+        dest.writeInt(controlType);
+        dest.writeInt(maxVolume);
+        dest.writeInt(currentVolume);
+        dest.writeInt(audioStream);
+    }
+
+
+    public static final Parcelable.Creator<ParcelableVolumeInfo> CREATOR
+            = new Parcelable.Creator<ParcelableVolumeInfo>() {
+        @Override
+        public ParcelableVolumeInfo createFromParcel(Parcel in) {
+            return new ParcelableVolumeInfo(in);
+        }
+
+        @Override
+        public ParcelableVolumeInfo[] newArray(int size) {
+            return new ParcelableVolumeInfo[size];
+        }
+    };
+}
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl b/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl
new file mode 100644
index 0000000..3d4ef59
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.support.v4.media.session;
+
+parcelable PlaybackStateCompat;
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
index 9152ab7..cb5fdb8 100644
--- a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -16,6 +16,7 @@
 package android.support.v4.media.session;
 
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -405,6 +406,176 @@
     };
 
     /**
+     * {@link PlaybackStateCompat.CustomAction CustomActions} can be used to
+     * extend the capabilities of the standard transport controls by exposing
+     * app specific actions to {@link MediaControllerCompat Controllers}.
+     */
+    public static final class CustomAction implements Parcelable {
+        private final String mAction;
+        private final CharSequence mName;
+        private final int mIcon;
+        private final Bundle mExtras;
+
+        /**
+         * Use {@link PlaybackStateCompat.CustomAction.Builder#build()}.
+         */
+        private CustomAction(String action, CharSequence name, int icon, Bundle extras) {
+            mAction = action;
+            mName = name;
+            mIcon = icon;
+            mExtras = extras;
+        }
+
+        private CustomAction(Parcel in) {
+            mAction = in.readString();
+            mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            mIcon = in.readInt();
+            mExtras = in.readBundle();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mAction);
+            TextUtils.writeToParcel(mName, dest, flags);
+            dest.writeInt(mIcon);
+            dest.writeBundle(mExtras);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Parcelable.Creator<PlaybackStateCompat.CustomAction> CREATOR
+                = new Parcelable.Creator<PlaybackStateCompat.CustomAction>() {
+
+                    @Override
+                    public PlaybackStateCompat.CustomAction createFromParcel(Parcel p) {
+                        return new PlaybackStateCompat.CustomAction(p);
+                    }
+
+                    @Override
+                    public PlaybackStateCompat.CustomAction[] newArray(int size) {
+                        return new PlaybackStateCompat.CustomAction[size];
+                    }
+                };
+
+        /**
+         * Returns the action of the {@link CustomAction}.
+         *
+         * @return The action of the {@link CustomAction}.
+         */
+        public String getAction() {
+            return mAction;
+        }
+
+        /**
+         * Returns the display name of this action. e.g. "Favorite"
+         *
+         * @return The display name of this {@link CustomAction}.
+         */
+        public CharSequence getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the resource id of the icon in the {@link MediaSessionCompat
+         * Session's} package.
+         *
+         * @return The resource id of the icon in the {@link MediaSessionCompat
+         *         Session's} package.
+         */
+        public int getIcon() {
+            return mIcon;
+        }
+
+        /**
+         * Returns extras which provide additional application-specific
+         * information about the action, or null if none. These arguments are
+         * meant to be consumed by a {@link MediaControllerCompat} if it knows
+         * how to handle them.
+         *
+         * @return Optional arguments for the {@link CustomAction}.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        @Override
+        public String toString() {
+            return "Action:" +
+                    "mName='" + mName +
+                    ", mIcon=" + mIcon +
+                    ", mExtras=" + mExtras;
+        }
+
+        /**
+         * Builder for {@link CustomAction} objects.
+         */
+        public static final class Builder {
+            private final String mAction;
+            private final CharSequence mName;
+            private final int mIcon;
+            private Bundle mExtras;
+
+            /**
+             * Creates a {@link CustomAction} builder with the id, name, and
+             * icon set.
+             *
+             * @param action The action of the {@link CustomAction}.
+             * @param name The display name of the {@link CustomAction}. This
+             *            name will be displayed along side the action if the UI
+             *            supports it.
+             * @param icon The icon resource id of the {@link CustomAction}.
+             *            This resource id must be in the same package as the
+             *            {@link MediaSessionCompat}. It will be displayed with
+             *            the custom action if the UI supports it.
+             */
+            public Builder(String action, CharSequence name, int icon) {
+                if (TextUtils.isEmpty(action)) {
+                    throw new IllegalArgumentException(
+                            "You must specify an action to build a CustomAction.");
+                }
+                if (TextUtils.isEmpty(name)) {
+                    throw new IllegalArgumentException(
+                            "You must specify a name to build a CustomAction.");
+                }
+                if (icon == 0) {
+                    throw new IllegalArgumentException(
+                            "You must specify an icon resource id to build a CustomAction.");
+                }
+                mAction = action;
+                mName = name;
+                mIcon = icon;
+            }
+
+            /**
+             * Set optional extras for the {@link CustomAction}. These extras
+             * are meant to be consumed by a {@link MediaControllerCompat} if it
+             * knows how to handle them. Keys should be fully qualified (e.g.
+             * "com.example.MY_ARG") to avoid collisions.
+             *
+             * @param extras Optional extras for the {@link CustomAction}.
+             * @return this.
+             */
+            public Builder setExtras(Bundle extras) {
+                mExtras = extras;
+                return this;
+            }
+
+            /**
+             * Build and return the {@link CustomAction} instance with the
+             * specified values.
+             *
+             * @return A new {@link CustomAction} instance.
+             */
+            public CustomAction build() {
+                return new CustomAction(mAction, mName, mIcon, mExtras);
+            }
+        }
+    }
+
+    /**
      * Builder for {@link PlaybackStateCompat} objects.
      */
     public static final class Builder {
@@ -441,12 +612,12 @@
         /**
          * Set the current state of playback.
          * <p>
-         * The position must be in ms and indicates the current playback position
-         * within the track. If the position is unknown use
+         * The position must be in ms and indicates the current playback
+         * position within the track. If the position is unknown use
          * {@link #PLAYBACK_POSITION_UNKNOWN}.
          * <p>
-         * The rate is a multiple of normal playback and should be 0 when paused and
-         * negative when rewinding. Normal playback rate is 1.0.
+         * The rate is a multiple of normal playback and should be 0 when paused
+         * and negative when rewinding. Normal playback rate is 1.0.
          * <p>
          * The state must be one of the following:
          * <ul>
@@ -462,28 +633,66 @@
          *
          * @param state The current state of playback.
          * @param position The position in the current track in ms.
-         * @param playbackRate The current rate of playback as a multiple of normal
-         *            playback.
+         * @param playbackSpeed The current rate of playback as a multiple of
+         *            normal playback.
          */
-        public void setState(int state, long position, float playbackRate) {
-            this.mState = state;
-            this.mPosition = position;
-            this.mRate = playbackRate;
-            mUpdateTime = SystemClock.elapsedRealtime();
+        public Builder setState(int state, long position, float playbackSpeed) {
+            return setState(state, position, playbackSpeed, SystemClock.elapsedRealtime());
+        }
+
+        /**
+         * Set the current state of playback.
+         * <p>
+         * The position must be in ms and indicates the current playback
+         * position within the track. If the position is unknown use
+         * {@link #PLAYBACK_POSITION_UNKNOWN}.
+         * <p>
+         * The rate is a multiple of normal playback and should be 0 when paused
+         * and negative when rewinding. Normal playback rate is 1.0.
+         * <p>
+         * The state must be one of the following:
+         * <ul>
+         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
+         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
+         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
+         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
+         * </ul>
+         *
+         * @param state The current state of playback.
+         * @param position The position in the current item in ms.
+         * @param playbackSpeed The current speed of playback as a multiple of
+         *            normal playback.
+         * @param updateTime The time in the {@link SystemClock#elapsedRealtime}
+         *            timebase that the position was updated at.
+         * @return this
+         */
+        public Builder setState(int state, long position, float playbackSpeed, long updateTime) {
+            mState = state;
+            mPosition = position;
+            mUpdateTime = updateTime;
+            mRate = playbackSpeed;
+            return this;
         }
 
         /**
          * Set the current buffered position in ms. This is the farthest
          * playback point that can be reached from the current position using
          * only buffered content.
+         *
+         * @return this
          */
-        public void setBufferedPosition(long bufferPosition) {
+        public Builder setBufferedPosition(long bufferPosition) {
             mBufferedPosition = bufferPosition;
+            return this;
         }
 
         /**
-         * Set the current capabilities available on this session. This should use a
-         * bitmask of the available capabilities.
+         * Set the current capabilities available on this session. This should
+         * use a bitmask of the available capabilities.
          * <ul>
          * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
          * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
@@ -495,17 +704,23 @@
          * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
          * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
          * </ul>
+         *
+         * @return this
          */
-        public void setActions(long capabilities) {
+        public Builder setActions(long capabilities) {
             mActions = capabilities;
+            return this;
         }
 
         /**
-         * Set a user readable error message. This should be set when the state is
-         * {@link PlaybackStateCompat#STATE_ERROR}.
+         * Set a user readable error message. This should be set when the state
+         * is {@link PlaybackStateCompat#STATE_ERROR}.
+         *
+         * @return this
          */
-        public void setErrorMessage(CharSequence errorMessage) {
+        public Builder setErrorMessage(CharSequence errorMessage) {
             mErrorMessage = errorMessage;
+            return this;
         }
 
         /**
diff --git a/v4/java/android/support/v4/util/CircularArray.java b/v4/java/android/support/v4/util/CircularArray.java
index 91a27da..760c986 100644
--- a/v4/java/android/support/v4/util/CircularArray.java
+++ b/v4/java/android/support/v4/util/CircularArray.java
@@ -14,11 +14,11 @@
 package android.support.v4.util;
 
 /**
- * A circular array implementation that provides O(1) random read and O(1)
- * prepend and O(1) append.
+ * CircularArray is a generic circular array data structure that provides O(1) random read, O(1)
+ * prepend and O(1) append. The CircularArray automatically grows its capacity when number of added
+ * items is over its capacity.
  */
-public class CircularArray<E>
-{
+public final class CircularArray<E> {
     private E[] mElements;
     private int mHead;
     private int mTail;
@@ -29,12 +29,12 @@
         int r = n - mHead;
         int newCapacity = n << 1;
         if (newCapacity < 0) {
-            throw new RuntimeException("Too big");
+            throw new RuntimeException("Max array capacity exceeded");
         }
         Object[] a = new Object[newCapacity];
         System.arraycopy(mElements, mHead, a, 0, r);
         System.arraycopy(mElements, 0, a, r, mHead);
-        mElements = (E[])a;
+        mElements = (E[]) a;
         mHead = 0;
         mTail = n;
         mCapacityBitmask = newCapacity - 1;
@@ -50,7 +50,7 @@
     /**
      * Create a CircularArray with capacity for at least minCapacity elements.
      *
-     * @param minCapacity The minimum capacity required for the circular array.
+     * @param minCapacity The minimum capacity required for the CircularArray.
      */
     public CircularArray(int minCapacity) {
         if (minCapacity <= 0) {
@@ -66,7 +66,11 @@
         mElements = (E[]) new Object[arrayCapacity];
     }
 
-    public final void addFirst(E e) {
+    /**
+     * Add an element in front of the CircularArray.
+     * @param e  Element to add.
+     */
+    public void addFirst(E e) {
         mHead = (mHead - 1) & mCapacityBitmask;
         mElements[mHead] = e;
         if (mHead == mTail) {
@@ -74,7 +78,11 @@
         }
     }
 
-    public final void addLast(E e) {
+    /**
+     * Add an element at end of the CircularArray.
+     * @param e  Element to add.
+     */
+    public void addLast(E e) {
         mElements[mTail] = e;
         mTail = (mTail + 1) & mCapacityBitmask;
         if (mTail == mHead) {
@@ -82,16 +90,30 @@
         }
     }
 
-    public final E popFirst() {
-        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+    /**
+     * Remove first element from front of the CircularArray and return it.
+     * @return  The element removed.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty.
+     */
+    public E popFirst() {
+        if (mHead == mTail) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
         E result = mElements[mHead];
         mElements[mHead] = null;
         mHead = (mHead + 1) & mCapacityBitmask;
         return result;
     }
 
-    public final E popLast() {
-        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+    /**
+     * Remove last element from end of the CircularArray and return it.
+     * @return  The element removed.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty.
+     */
+    public E popLast() {
+        if (mHead == mTail) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
         int t = (mTail - 1) & mCapacityBitmask;
         E result = mElements[t];
         mElements[t] = null;
@@ -99,27 +121,131 @@
         return result;
     }
 
-    public final E getFirst() {
-        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+    /**
+     * Remove all elements from the CircularArray.
+     */
+    public void clear() {
+        removeFromStart(size());
+    }
+
+    /**
+     * Remove multiple elements from front of the CircularArray, ignore when numOfElements
+     * is less than or equals to 0.
+     * @param numOfElements  Number of elements to remove.
+     * @throws {@link ArrayIndexOutOfBoundsException} if numOfElements is larger than
+     *         {@link #size()}
+     */
+    public void removeFromStart(int numOfElements) {
+        if (numOfElements <= 0) {
+            return;
+        }
+        if (numOfElements > size()) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int end = mElements.length;
+        if (numOfElements < end - mHead) {
+            end = mHead + numOfElements;
+        }
+        for (int i = mHead; i < end; i++) {
+            mElements[i] = null;
+        }
+        int removed = (end - mHead);
+        numOfElements -= removed;
+        mHead = (mHead + removed) & mCapacityBitmask;
+        if (numOfElements > 0) {
+            // mHead wrapped to 0
+            for (int i = 0; i < numOfElements; i++) {
+                mElements[i] = null;
+            }
+            mHead = numOfElements;
+        }
+    }
+
+    /**
+     * Remove multiple elements from end of the CircularArray, ignore when numOfElements
+     * is less than or equals to 0.
+     * @param numOfElements  Number of elements to remove.
+     * @throws {@link ArrayIndexOutOfBoundsException} if numOfElements is larger than
+     *         {@link #size()}
+     */
+    public void removeFromEnd(int numOfElements) {
+        if (numOfElements <= 0) {
+            return;
+        }
+        if (numOfElements > size()) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int start = 0;
+        if (numOfElements < mTail) {
+            start = mTail - numOfElements;
+        }
+        for (int i = start; i < mTail; i++) {
+            mElements[i] = null;
+        }
+        int removed = (mTail - start);
+        numOfElements -= removed;
+        mTail = mTail - removed;
+        if (numOfElements > 0) {
+            // mTail wrapped to mElements.length
+            mTail = mElements.length;
+            int newTail = mTail - numOfElements;
+            for (int i = newTail; i < mTail; i++) {
+                mElements[i] = null;
+            }
+            mTail = newTail;
+        }
+    }
+
+    /**
+     * Get first element of the CircularArray.
+     * @return The first element.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty.
+     */
+    public E getFirst() {
+        if (mHead == mTail) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
         return mElements[mHead];
     }
 
-    public final E getLast() {
-        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+    /**
+     * Get last element of the CircularArray.
+     * @return The last element.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularArray is empty.
+     */
+    public E getLast() {
+        if (mHead == mTail) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
         return mElements[(mTail - 1) & mCapacityBitmask];
     }
 
-    public final E get(int i) {
-        if (i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException();
-        int p = (mHead + i) & mCapacityBitmask;
-        return mElements[p];
+    /**
+     * Get nth (0 <= n <= size()-1) element of the CircularArray.
+     * @param n  The zero based element index in the CircularArray.
+     * @return The nth element.
+     * @throws {@link ArrayIndexOutOfBoundsException} if n < 0 or n >= size().
+     */
+    public E get(int n) {
+        if (n < 0 || n >= size()) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return mElements[(mHead + n) & mCapacityBitmask];
     }
 
-    public final int size() {
+    /**
+     * Get number of elements in the CircularArray.
+     * @return Number of elements in the CircularArray.
+     */
+    public int size() {
         return (mTail - mHead) & mCapacityBitmask;
     }
 
-    public final boolean isEmpty() {
+    /**
+     * Return true if size() is 0.
+     * @return true if size() is 0.
+     */
+    public boolean isEmpty() {
         return mHead == mTail;
     }
 
diff --git a/v4/java/android/support/v4/util/CircularIntArray.java b/v4/java/android/support/v4/util/CircularIntArray.java
new file mode 100644
index 0000000..2389436
--- /dev/null
+++ b/v4/java/android/support/v4/util/CircularIntArray.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v4.util;
+
+/**
+ * CircularIntArray is a circular integer array data structure that provides O(1) random read, O(1)
+ * prepend and O(1) append. The CircularIntArray automatically grows its capacity when number of
+ * added integers is over its capacity.
+ */
+public final class CircularIntArray
+{
+    private int[] mElements;
+    private int mHead;
+    private int mTail;
+    private int mCapacityBitmask;
+
+    private void doubleCapacity() {
+        int n = mElements.length;
+        int r = n - mHead;
+        int newCapacity = n << 1;
+        if (newCapacity < 0) {
+            throw new RuntimeException("Max array capacity exceeded");
+        }
+        int[] a = new int[newCapacity];
+        System.arraycopy(mElements, mHead, a, 0, r);
+        System.arraycopy(mElements, 0, a, r, mHead);
+        mElements = a;
+        mHead = 0;
+        mTail = n;
+        mCapacityBitmask = newCapacity - 1;
+    }
+
+    /**
+     * Create a CircularIntArray with default capacity.
+     */
+    public CircularIntArray() {
+        this(8);
+    }
+
+    /**
+     * Create a CircularIntArray with capacity for at least minCapacity elements.
+     *
+     * @param minCapacity The minimum capacity required for the CircularIntArray.
+     */
+    public CircularIntArray(int minCapacity) {
+        if (minCapacity <= 0) {
+            throw new IllegalArgumentException("capacity must be positive");
+        }
+        int arrayCapacity = minCapacity;
+        // If minCapacity isn't a power of 2, round up to the next highest power
+        // of 2.
+        if (Integer.bitCount(minCapacity) != 1) {
+            arrayCapacity = 1 << (Integer.highestOneBit(minCapacity) + 1);
+        }
+        mCapacityBitmask = arrayCapacity - 1;
+        mElements = new int[arrayCapacity];
+    }
+
+    /**
+     * Add an integer in front of the CircularIntArray.
+     * @param e  Integer to add.
+     */
+    public void addFirst(int e) {
+        mHead = (mHead - 1) & mCapacityBitmask;
+        mElements[mHead] = e;
+        if (mHead == mTail) {
+            doubleCapacity();
+        }
+    }
+
+    /**
+     * Add an integer at end of the CircularIntArray.
+     * @param e  Integer to add.
+     */
+    public void addLast(int e) {
+        mElements[mTail] = e;
+        mTail = (mTail + 1) & mCapacityBitmask;
+        if (mTail == mHead) {
+            doubleCapacity();
+        }
+    }
+
+    /**
+     * Remove first integer from front of the CircularIntArray and return it.
+     * @return  The integer removed.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularIntArray is empty.
+     */
+    public int popFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        int result = mElements[mHead];
+        mHead = (mHead + 1) & mCapacityBitmask;
+        return result;
+    }
+
+    /**
+     * Remove last integer from end of the CircularIntArray and return it.
+     * @return  The integer removed.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularIntArray is empty.
+     */
+    public int popLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        int t = (mTail - 1) & mCapacityBitmask;
+        int result = mElements[t];
+        mTail = t;
+        return result;
+    }
+
+    /**
+     * Remove all integers from the CircularIntArray.
+     */
+    public void clear() {
+        mTail = mHead;
+    }
+
+    /**
+     * Remove multiple integers from front of the CircularIntArray, ignore when numOfElements
+     * is less than or equals to 0.
+     * @param numOfElements  Number of integers to remove.
+     * @throws {@link ArrayIndexOutOfBoundsException} if numOfElements is larger than
+     *         {@link #size()}
+     */
+    public void removeFromStart(int numOfElements) {
+        if (numOfElements <= 0) {
+            return;
+        }
+        if (numOfElements > size()) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        mHead = (mHead + numOfElements) & mCapacityBitmask;
+    }
+
+    /**
+     * Remove multiple elements from end of the CircularIntArray, ignore when numOfElements
+     * is less than or equals to 0.
+     * @param numOfElements  Number of integers to remove.
+     * @throws {@link ArrayIndexOutOfBoundsException} if numOfElements is larger than
+     *         {@link #size()}
+     */
+    public void removeFromEnd(int numOfElements) {
+        if (numOfElements <= 0) {
+            return;
+        }
+        if (numOfElements > size()) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        mTail = (mTail - numOfElements) & mCapacityBitmask;
+    }
+
+    /**
+     * Get first integer of the CircularIntArray.
+     * @return The first integer.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularIntArray is empty.
+     */
+    public int getFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[mHead];
+    }
+
+    /**
+     * Get last integer of the CircularIntArray.
+     * @return The last integer.
+     * @throws {@link ArrayIndexOutOfBoundsException} if CircularIntArray is empty.
+     */
+    public int getLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[(mTail - 1) & mCapacityBitmask];
+    }
+
+    /**
+     * Get nth (0 <= n <= size()-1) integer of the CircularIntArray.
+     * @param n  The zero based element index in the CircularIntArray.
+     * @return The nth integer.
+     * @throws {@link ArrayIndexOutOfBoundsException} if n < 0 or n >= size().
+     */
+    public int get(int n) {
+        if (n < 0 || n >= size()) throw new ArrayIndexOutOfBoundsException();
+        return mElements[(mHead + n) & mCapacityBitmask];
+    }
+
+    /**
+     * Get number of integers in the CircularIntArray.
+     * @return Number of integers in the CircularIntArray.
+     */
+    public int size() {
+        return (mTail - mHead) & mCapacityBitmask;
+    }
+
+    /**
+     * Return true if size() is 0.
+     * @return true if size() is 0.
+     */
+    public boolean isEmpty() {
+        return mHead == mTail;
+    }
+
+}
diff --git a/v4/java/android/support/v4/view/InputDeviceCompat.java b/v4/java/android/support/v4/view/InputDeviceCompat.java
new file mode 100644
index 0000000..7a202cd
--- /dev/null
+++ b/v4/java/android/support/v4/view/InputDeviceCompat.java
@@ -0,0 +1,211 @@
+/*
+ * 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.v4.view;
+
+/**
+ * Helper class for accessing values in {@link android.view.InputDevice}.
+ */
+public class InputDeviceCompat {
+
+    /**
+     * A mask for input source classes.
+     *
+     * Each distinct input source constant has one or more input source class bits set to
+     * specify the desired interpretation for its input events.
+     */
+    public static final int SOURCE_CLASS_MASK = 0x000000ff;
+
+    /**
+     * The input source has no class.
+     *
+     * It is up to the application to determine how to handle the device based on the device type.
+     */
+    public static final int SOURCE_CLASS_NONE = 0x00000000;
+
+    /**
+     * The input source has buttons or keys.
+     * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_DPAD}.
+     *
+     * A {@link android.view.KeyEvent} should be interpreted as a button or key press.
+     */
+    public static final int SOURCE_CLASS_BUTTON = 0x00000001;
+
+    /**
+     * The input source is a pointing device associated with a display.
+     * Examples: {@link #SOURCE_TOUCHSCREEN}, {@link #SOURCE_MOUSE}.
+     *
+     * A {@link android.view.MotionEvent} should be interpreted as absolute coordinates in
+     * display units according to the {@link android.view.View} hierarchy.  Pointer down/up
+     * indicated when
+     * the finger touches the display or when the selection button is pressed/released.
+     *
+     * Use {@link android.view.InputDevice#getMotionRange} to query the range of the pointing
+     * device.  Some devices permit
+     * touches outside the display area so the effective range may be somewhat smaller or larger
+     * than the actual display size.
+     */
+    public static final int SOURCE_CLASS_POINTER = 0x00000002;
+
+    /**
+     * The input source is a trackball navigation device.
+     * Examples: {@link #SOURCE_TRACKBALL}.
+     *
+     * A {@link android.view.MotionEvent} should be interpreted as relative movements in
+     * device-specific
+     * units used for navigation purposes.  Pointer down/up indicates when the selection button
+     * is pressed/released.
+     *
+     * Use {@link android.view.InputDevice#getMotionRange} to query the range of motion.
+     */
+    public static final int SOURCE_CLASS_TRACKBALL = 0x00000004;
+
+    /**
+     * The input source is an absolute positioning device not associated with a display
+     * (unlike {@link #SOURCE_CLASS_POINTER}).
+     *
+     * A {@link android.view.MotionEvent} should be interpreted as absolute coordinates in
+     * device-specific surface units.
+     *
+     * Use {@link android.view.InputDevice#getMotionRange} to query the range of positions.
+     */
+    public static final int SOURCE_CLASS_POSITION = 0x00000008;
+
+    /**
+     * The input source is a joystick.
+     *
+     * A {@link android.view.MotionEvent} should be interpreted as absolute joystick movements.
+     *
+     * Use {@link android.view.InputDevice#getMotionRange} to query the range of positions.
+     */
+    public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
+
+    /**
+     * The input source is unknown.
+     */
+    public static final int SOURCE_UNKNOWN = 0x00000000;
+
+    /**
+     * The input source is a keyboard.
+     *
+     * This source indicates pretty much anything that has buttons.  Use
+     * {@link android.view.InputDevice#getKeyboardType()} to determine whether the keyboard has
+     * alphabetic keys
+     * and can be used to enter text.
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a DPad.
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a game pad.
+     * (It may also be a {@link #SOURCE_JOYSTICK}).
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * The input source is a touch screen pointing device.
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input source is a mouse pointing device.
+     * This code is also used for other mouse-like pointing devices such as trackpads
+     * and trackpoints.
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input source is a stylus pointing device.
+     * <p>
+     * Note that this bit merely indicates that an input device is capable of obtaining
+     * input from a stylus.  To determine whether a given touch event was produced
+     * by a stylus, examine the tool type returned by {@link android.view.MotionEvent#getToolType(int)}
+     * for each individual pointer.
+     * </p><p>
+     * A single touch event may multiple pointers with different tool types,
+     * such as an event that has one pointer with tool type
+     * {@link android.view.MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
+     * {@link android.view.MotionEvent#TOOL_TYPE_STYLUS}.  So it is important to examine
+     * the tool type of each pointer, regardless of the source reported
+     * by {@link android.view.MotionEvent#getSource()}.
+     * </p>
+     *
+     * @see #SOURCE_CLASS_POINTER
+     */
+    public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;
+
+    /**
+     * The input source is a trackball.
+     *
+     * @see #SOURCE_CLASS_TRACKBALL
+     */
+    public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
+
+    /**
+     * The input source is a touch pad or digitizer tablet that is not
+     * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+     *
+     * @see #SOURCE_CLASS_POSITION
+     */
+    public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
+
+    /**
+     * The input source is a touch device whose motions should be interpreted as navigation events.
+     *
+     * For example, an upward swipe should be as an upward focus traversal in the same manner as
+     * pressing up on a D-Pad would be. Swipes to the left, right and down should be treated in a
+     * similar manner.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_TOUCH_NAVIGATION = 0x00200000 | SOURCE_CLASS_NONE;
+
+    /**
+     * The input source is a joystick.
+     * (It may also be a {@link #SOURCE_GAMEPAD}).
+     *
+     * @see #SOURCE_CLASS_JOYSTICK
+     */
+    public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK;
+
+    /**
+     * The input source is a device connected through HDMI-based bus.
+     *
+     * The key comes in through HDMI-CEC or MHL signal line, and is treated as if it were
+     * generated by a locally connected DPAD or keyboard.
+     */
+    public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
+
+    /**
+     * A special input source constant that is used when filtering input devices
+     * to match devices that provide any type of input source.
+     */
+    public static final int SOURCE_ANY = 0xffffff00;
+}
diff --git a/v4/java/android/support/v4/view/LayoutInflaterCompat.java b/v4/java/android/support/v4/view/LayoutInflaterCompat.java
new file mode 100644
index 0000000..56ed8f3
--- /dev/null
+++ b/v4/java/android/support/v4/view/LayoutInflaterCompat.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.os.Build;
+import android.view.LayoutInflater;
+
+/**
+ * Helper for accessing features in {@link android.view.LayoutInflater}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class LayoutInflaterCompat {
+
+    interface LayoutInflaterCompatImpl {
+        public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory);
+    }
+
+    static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
+        @Override
+        public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
+            LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
+        }
+    }
+
+    static class LayoutInflaterCompatImplV11 extends LayoutInflaterCompatImplBase {
+        @Override
+        public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
+            LayoutInflaterCompatHC.setFactory(layoutInflater, factory);
+        }
+    }
+
+    static final LayoutInflaterCompatImpl IMPL;
+    static {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 11) {
+            IMPL = new LayoutInflaterCompatImplV11();
+        } else {
+            IMPL = new LayoutInflaterCompatImplBase();
+        }
+    }
+
+    /*
+     * Hide the constructor.
+     */
+    private LayoutInflaterCompat() {
+    }
+
+    /**
+     * Attach a custom Factory interface for creating views while using
+     * this LayoutInflater. This must not be null, and can only be set once;
+     * after setting, you can not change the factory.
+     *
+     * @see LayoutInflater#setFactory(android.view.LayoutInflater.Factory)
+     */
+    public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
+        IMPL.setFactory(inflater, factory);
+    }
+
+}
diff --git a/v4/java/android/support/v4/view/MotionEventCompat.java b/v4/java/android/support/v4/view/MotionEventCompat.java
index 18e9e5d..8c7d07d 100644
--- a/v4/java/android/support/v4/view/MotionEventCompat.java
+++ b/v4/java/android/support/v4/view/MotionEventCompat.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.view;
 
+import android.os.Build;
 import android.view.MotionEvent;
 
 /**
@@ -32,6 +33,9 @@
         public float getX(MotionEvent event, int pointerIndex);
         public float getY(MotionEvent event, int pointerIndex);
         public int getPointerCount(MotionEvent event);
+        public int getSource(MotionEvent event);
+        float getAxisValue(MotionEvent event, int axis);
+        float getAxisValue(MotionEvent event, int axis, int pointerIndex);
     }
 
     /**
@@ -72,12 +76,27 @@
         public int getPointerCount(MotionEvent event) {
             return 1;
         }
+
+        @Override
+        public int getSource(MotionEvent event) {
+            return InputDeviceCompat.SOURCE_UNKNOWN;
+        }
+
+        @Override
+        public float getAxisValue(MotionEvent event, int axis) {
+            return 0;
+        }
+
+        @Override
+        public float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
+            return 0;
+        }
     }
 
     /**
-     * Interface implementation for devices with at least v11 APIs.
+     * Interface implementation for devices with at least v5 APIs.
      */
-    static class EclairMotionEventVersionImpl implements MotionEventVersionImpl {
+    static class EclairMotionEventVersionImpl extends BaseMotionEventVersionImpl {
         @Override
         public int findPointerIndex(MotionEvent event, int pointerId) {
             return MotionEventCompatEclair.findPointerIndex(event, pointerId);
@@ -101,11 +120,41 @@
     }
 
     /**
+     * Interface implementation for devices with at least v8 APIs.
+     */
+    static class GingerbreadMotionEventVersionImpl extends EclairMotionEventVersionImpl {
+        @Override
+        public int getSource(MotionEvent event) {
+            return MotionEventCompatGingerbread.getSource(event);
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v12 APIs.
+     */
+    static class HoneycombMr1MotionEventVersionImpl extends GingerbreadMotionEventVersionImpl {
+
+        @Override
+        public float getAxisValue(MotionEvent event, int axis) {
+            return MotionEventCompatHoneycombMr1.getAxisValue(event, axis);
+        }
+
+        @Override
+        public float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
+            return MotionEventCompatHoneycombMr1.getAxisValue(event, axis, pointerIndex);
+        }
+    }
+
+    /**
      * Select the correct implementation to use for the current platform.
      */
     static final MotionEventVersionImpl IMPL;
     static {
-        if (android.os.Build.VERSION.SDK_INT >= 5) {
+        if (Build.VERSION.SDK_INT >= 12) {
+            IMPL = new HoneycombMr1MotionEventVersionImpl();
+        } else if (Build.VERSION.SDK_INT >= 9) {
+            IMPL = new GingerbreadMotionEventVersionImpl();
+        } else if (Build.VERSION.SDK_INT >= 5) {
             IMPL = new EclairMotionEventVersionImpl();
         } else {
             IMPL = new BaseMotionEventVersionImpl();
@@ -150,32 +199,226 @@
     public static final int ACTION_POINTER_INDEX_SHIFT = 8;
 
     /**
-     * Constant for {@link #getActionMasked}: The pointer is not down but has entered the
-     * boundaries of a window or view.
-     * <p>
-     * This action is always delivered to the window or view under the pointer.
-     * </p><p>
-     * This action is not a touch event so it is delivered to
-     * {@link android.view.View#onGenericMotionEvent(MotionEvent)} rather than
-     * {@link android.view.View#onTouchEvent(MotionEvent)}.
-     * </p>
+     * Synonym for {@link MotionEvent#ACTION_HOVER_ENTER}.
      */
     public static final int ACTION_HOVER_ENTER = 9;
 
     /**
-     * Constant for {@link #getActionMasked}: The pointer is not down but has exited the
-     * boundaries of a window or view.
-     * <p>
-     * This action is always delivered to the window or view that was previously under the pointer.
-     * </p><p>
-     * This action is not a touch event so it is delivered to
-     * {@link android.view.View#onGenericMotionEvent(MotionEvent)} rather than
-     * {@link android.view.View#onTouchEvent(MotionEvent)}.
-     * </p>
+     * Synonym for {@link MotionEvent#ACTION_HOVER_EXIT}.
      */
     public static final int ACTION_HOVER_EXIT = 10;
 
     /**
+     * Synonym for {@link MotionEvent#AXIS_X}.
+     */
+    public static final int AXIS_X = 0;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_Y}.
+     */
+    public static final int AXIS_Y = 1;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_PRESSURE}.
+     */
+    public static final int AXIS_PRESSURE = 2;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_SIZE}.
+     */
+    public static final int AXIS_SIZE = 3;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_TOUCH_MAJOR}.
+     */
+    public static final int AXIS_TOUCH_MAJOR = 4;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_TOUCH_MINOR}.
+     */
+    public static final int AXIS_TOUCH_MINOR = 5;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_TOOL_MAJOR}.
+     */
+    public static final int AXIS_TOOL_MAJOR = 6;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_TOOL_MINOR}.
+     */
+    public static final int AXIS_TOOL_MINOR = 7;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_ORIENTATION}.
+     */
+    public static final int AXIS_ORIENTATION = 8;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_VSCROLL}.
+     */
+    public static final int AXIS_VSCROLL = 9;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_HSCROLL}.
+     */
+    public static final int AXIS_HSCROLL = 10;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_Z}.
+     */
+    public static final int AXIS_Z = 11;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RX}.
+     */
+    public static final int AXIS_RX = 12;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RY}.
+     */
+    public static final int AXIS_RY = 13;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RZ}.
+     */
+    public static final int AXIS_RZ = 14;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_HAT_X}.
+     */
+    public static final int AXIS_HAT_X = 15;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_HAT_Y}.
+     */
+    public static final int AXIS_HAT_Y = 16;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_LTRIGGER}.
+     */
+    public static final int AXIS_LTRIGGER = 17;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RTRIGGER}.
+     */
+    public static final int AXIS_RTRIGGER = 18;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_THROTTLE}.
+     */
+    public static final int AXIS_THROTTLE = 19;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RUDDER}.
+     */
+    public static final int AXIS_RUDDER = 20;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_WHEEL}.
+     */
+    public static final int AXIS_WHEEL = 21;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GAS}.
+     */
+    public static final int AXIS_GAS = 22;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_BRAKE}.
+     */
+    public static final int AXIS_BRAKE = 23;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_DISTANCE}.
+     */
+    public static final int AXIS_DISTANCE = 24;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_TILT}.
+     */
+    public static final int AXIS_TILT = 25;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_1}.
+     */
+    public static final int AXIS_GENERIC_1 = 32;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_2}.
+     */
+    public static final int AXIS_GENERIC_2 = 33;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_3}.
+     */
+    public static final int AXIS_GENERIC_3 = 34;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_4}.
+     */
+    public static final int AXIS_GENERIC_4 = 35;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_5}.
+     */
+    public static final int AXIS_GENERIC_5 = 36;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_6}.
+     */
+    public static final int AXIS_GENERIC_6 = 37;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_7}.
+     */
+    public static final int AXIS_GENERIC_7 = 38;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_8}.
+     */
+    public static final int AXIS_GENERIC_8 = 39;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_9}.
+     */
+    public static final int AXIS_GENERIC_9 = 40;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_10}.
+     */
+    public static final int AXIS_GENERIC_10 = 41;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_11}.
+     */
+    public static final int AXIS_GENERIC_11 = 42;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_12}.
+     */
+    public static final int AXIS_GENERIC_12 = 43;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_13}.
+     */
+    public static final int AXIS_GENERIC_13 = 44;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_14}.
+     */
+    public static final int AXIS_GENERIC_14 = 45;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_15}.
+     */
+    public static final int AXIS_GENERIC_15 = 46;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_GENERIC_16}.
+     */
+    public static final int AXIS_GENERIC_16 = 47;
+
+    /**
      * Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
      * portion.
      */
@@ -235,4 +478,42 @@
     public static int getPointerCount(MotionEvent event) {
         return IMPL.getPointerCount(event);
     }
+
+    /**
+     * Gets the source of the event.
+     *
+     * @return The event source or {@link InputDeviceCompat#SOURCE_UNKNOWN} if unknown.
+     */
+    public static int getSource(MotionEvent event) {
+        return IMPL.getSource(event);
+    }
+
+    /**
+     * Get axis value for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     *
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public static float getAxisValue(MotionEvent event, int axis) {
+        return IMPL.getAxisValue(event, axis);
+    }
+
+    /**
+     * Returns the value of the requested axis for the given pointer <em>index</em>
+     * (use {@link #getPointerId(MotionEvent, int)} to find the pointer identifier for this index).
+     *
+     * @param axis The axis identifier for the axis value to retrieve.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount(MotionEvent)}-1.
+     * @return The value of the axis, or 0 if the axis is not available.
+     *
+     * @see #AXIS_X
+     * @see #AXIS_Y
+     */
+    public static float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
+        return IMPL.getAxisValue(event, axis, pointerIndex);
+    }
 }
diff --git a/v4/java/android/support/v4/view/NestedScrollingChild.java b/v4/java/android/support/v4/view/NestedScrollingChild.java
new file mode 100644
index 0000000..40e0a09
--- /dev/null
+++ b/v4/java/android/support/v4/view/NestedScrollingChild.java
@@ -0,0 +1,227 @@
+/*
+ * 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.v4.view;
+
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+/**
+ * This interface should be implemented by {@link android.view.View View} subclasses that wish
+ * to support dispatching nested scrolling operations to a cooperating parent
+ * {@link android.view.ViewGroup ViewGroup}.
+ *
+ * <p>Classes implementing this interface should create a final instance of a
+ * {@link NestedScrollingChildHelper} as a field and delegate any View methods to the
+ * <code>NestedScrollingChildHelper</code> methods of the same signature.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public interface NestedScrollingChild {
+    /**
+     * Enable or disable nested scrolling for this view.
+     *
+     * <p>If this property is set to true the view will be permitted to initiate nested
+     * scrolling operations with a compatible parent view in the current hierarchy. If this
+     * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+     * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+     * the nested scroll.</p>
+     *
+     * @param enabled true to enable nested scrolling, false to disable
+     *
+     * @see #isNestedScrollingEnabled()
+     */
+    public void setNestedScrollingEnabled(boolean enabled);
+
+    /**
+     * Returns true if nested scrolling is enabled for this view.
+     *
+     * <p>If nested scrolling is enabled and this View class implementation supports it,
+     * this view will act as a nested scrolling child view when applicable, forwarding data
+     * about the scroll operation in progress to a compatible and cooperating nested scrolling
+     * parent.</p>
+     *
+     * @return true if nested scrolling is enabled
+     *
+     * @see #setNestedScrollingEnabled(boolean)
+     */
+    public boolean isNestedScrollingEnabled();
+
+    /**
+     * Begin a nestable scroll operation along the given axes.
+     *
+     * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+     *
+     * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+     * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+     * In the case of touch scrolling the nested scroll will be terminated automatically in
+     * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+     * In the event of programmatic scrolling the caller must explicitly call
+     * {@link #stopNestedScroll()} to indicate the end of the nested scroll.</p>
+     *
+     * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+     * If it returns false the caller may ignore the rest of this contract until the next scroll.
+     * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+     *
+     * <p>At each incremental step of the scroll the caller should invoke
+     * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll}
+     * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+     * parent at least partially consumed the scroll and the caller should adjust the amount it
+     * scrolls by.</p>
+     *
+     * <p>After applying the remainder of the scroll delta the caller should invoke
+     * {@link #dispatchNestedScroll(int, int, int, int, int[]) dispatchNestedScroll}, passing
+     * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+     * these values differently. See
+     * {@link NestedScrollingParent#onNestedScroll(View, int, int, int, int)}.
+     * </p>
+     *
+     * @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
+     *             and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
+     * @return true if a cooperative parent was found and nested scrolling has been enabled for
+     *         the current gesture.
+     *
+     * @see #stopNestedScroll()
+     * @see #dispatchNestedPreScroll(int, int, int[], int[])
+     * @see #dispatchNestedScroll(int, int, int, int, int[])
+     */
+    public boolean startNestedScroll(int axes);
+
+    /**
+     * Stop a nested scroll in progress.
+     *
+     * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+     *
+     * @see #startNestedScroll(int)
+     */
+    public void stopNestedScroll();
+
+    /**
+     * Returns true if this view has a nested scrolling parent.
+     *
+     * <p>The presence of a nested scrolling parent indicates that this view has initiated
+     * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+     *
+     * @return whether this view has a nested scrolling parent
+     */
+    public boolean hasNestedScrollingParent();
+
+    /**
+     * Dispatch one step of a nested scroll in progress.
+     *
+     * <p>Implementations of views that support nested scrolling should call this to report
+     * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+     * is not currently in progress or nested scrolling is not
+     * {@link #isNestedScrollingEnabled() enabled} for this view this method does nothing.</p>
+     *
+     * <p>Compatible View implementations should also call
+     * {@link #dispatchNestedPreScroll(int, int, int[], int[]) dispatchNestedPreScroll} before
+     * consuming a component of the scroll event themselves.</p>
+     *
+     * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+     * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the event was dispatched, false if it could not be dispatched.
+     * @see #dispatchNestedPreScroll(int, int, int[], int[])
+     */
+    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
+
+    /**
+     * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+     *
+     * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+     * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+     * scrolling operation to consume some or all of the scroll operation before the child view
+     * consumes it.</p>
+     *
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+     *                 and consumed[1] the consumed dy.
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the parent consumed some or all of the scroll delta
+     * @see #dispatchNestedScroll(int, int, int, int, int[])
+     */
+    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
+
+    /**
+     * Dispatch a fling to a nested scrolling parent.
+     *
+     * <p>This method should be used to indicate that a nested scrolling child has detected
+     * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+     */
+    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
+
+    /**
+     * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+     *
+     * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+     * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+     * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+     * before the child view consumes it. If this method returns <code>true</code>, a nested
+     * parent view consumed the fling and this view should not scroll as a result.</p>
+     *
+     * <p>For a better user experience, only one view in a nested scrolling chain should consume
+     * the fling at a time. If a parent view consumed the fling this method will return false.
+     * Custom view implementations should account for this in two ways:</p>
+     *
+     * <ul>
+     *     <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+     *     call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+     *     position regardless.</li>
+     *     <li>If a nested parent does consume the fling, this view should not scroll at all,
+     *     even to settle back to a valid idle position.</li>
+     * </ul>
+     *
+     * <p>Views should also not offer fling velocities to nested parent views along an axis
+     * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+     * should not offer a horizontal fling velocity to its parents since scrolling along that
+     * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @return true if a nested scrolling parent consumed the fling
+     */
+    public boolean dispatchNestedPreFling(float velocityX, float velocityY);
+}
diff --git a/v4/java/android/support/v4/view/NestedScrollingChildHelper.java b/v4/java/android/support/v4/view/NestedScrollingChildHelper.java
new file mode 100644
index 0000000..9e25667
--- /dev/null
+++ b/v4/java/android/support/v4/view/NestedScrollingChildHelper.java
@@ -0,0 +1,282 @@
+/*
+ * 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.v4.view;
+
+import android.view.View;
+import android.view.ViewParent;
+
+/**
+ * Helper class for implementing nested scrolling child views compatible with Android platform
+ * versions earlier than Android 5.0 Lollipop (API 21).
+ *
+ * <p>{@link android.view.View View} subclasses should instantiate a final instance of this
+ * class as a field at construction. For each <code>View</code> method that has a matching
+ * method signature in this class, delegate the operation to the helper instance in an overriden
+ * method implementation. This implements the standard framework policy for nested scrolling.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public class NestedScrollingChildHelper {
+    private final View mView;
+    private ViewParent mNestedScrollingParent;
+    private boolean mIsNestedScrollingEnabled;
+    private int[] mTempNestedScrollConsumed;
+
+    /**
+     * Construct a new helper for a given view.
+     */
+    public NestedScrollingChildHelper(View view) {
+        mView = view;
+    }
+
+    /**
+     * Enable nested scrolling.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @param enabled true to enable nested scrolling dispatch from this view, false otherwise
+     */
+    public void setNestedScrollingEnabled(boolean enabled) {
+        if (mIsNestedScrollingEnabled) {
+            ViewCompat.stopNestedScroll(mView);
+        }
+        mIsNestedScrollingEnabled = enabled;
+    }
+
+    /**
+     * Check if nested scrolling is enabled for this view.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if nested scrolling is enabled for this view
+     */
+    public boolean isNestedScrollingEnabled() {
+        return mIsNestedScrollingEnabled;
+    }
+
+    /**
+     * Check if this view has a nested scrolling parent view currently receiving events for
+     * a nested scroll in progress.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if this view has a nested scrolling parent, false otherwise
+     */
+    public boolean hasNestedScrollingParent() {
+        return mNestedScrollingParent != null;
+    }
+
+    /**
+     * Start a new nested scroll for this view.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @param axes Supported nested scroll axes.
+     *             See {@link NestedScrollingChild#startNestedScroll(int)}.
+     * @return true if a cooperating parent view was found and nested scrolling started successfully
+     */
+    public boolean startNestedScroll(int axes) {
+        if (hasNestedScrollingParent()) {
+            // Already in progress
+            return true;
+        }
+        if (isNestedScrollingEnabled()) {
+            ViewParent p = mView.getParent();
+            View child = mView;
+            while (p != null) {
+                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
+                    mNestedScrollingParent = p;
+                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
+                    return true;
+                }
+                if (p instanceof View) {
+                    child = (View) p;
+                }
+                p = p.getParent();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Stop a nested scroll in progress.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     */
+    public void stopNestedScroll() {
+        if (mNestedScrollingParent != null) {
+            ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
+            mNestedScrollingParent = null;
+        }
+    }
+
+    /**
+     * Dispatch one step of a nested scrolling operation to the current nested scrolling parent.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if the parent consumed any of the nested scroll
+     */
+    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+                int startX = 0;
+                int startY = 0;
+                if (offsetInWindow != null) {
+                    mView.getLocationInWindow(offsetInWindow);
+                    startX = offsetInWindow[0];
+                    startY = offsetInWindow[1];
+                }
+
+                ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
+                        dyConsumed, dxUnconsumed, dyUnconsumed);
+
+                if (offsetInWindow != null) {
+                    mView.getLocationInWindow(offsetInWindow);
+                    offsetInWindow[0] -= startX;
+                    offsetInWindow[1] -= startY;
+                }
+                return true;
+            } else if (offsetInWindow != null) {
+                // No motion, no dispatch. Keep offsetInWindow up to date.
+                offsetInWindow[0] = 0;
+                offsetInWindow[1] = 0;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if the parent consumed any of the nested scroll
+     */
+    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            if (dx != 0 || dy != 0) {
+                int startX = 0;
+                int startY = 0;
+                if (offsetInWindow != null) {
+                    mView.getLocationInWindow(offsetInWindow);
+                    startX = offsetInWindow[0];
+                    startY = offsetInWindow[1];
+                }
+
+                if (consumed == null) {
+                    if (mTempNestedScrollConsumed == null) {
+                        mTempNestedScrollConsumed = new int[2];
+                    }
+                    consumed = mTempNestedScrollConsumed;
+                }
+                consumed[0] = 0;
+                consumed[1] = 0;
+                ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
+
+                if (offsetInWindow != null) {
+                    mView.getLocationInWindow(offsetInWindow);
+                    offsetInWindow[0] -= startX;
+                    offsetInWindow[1] -= startY;
+                }
+                return consumed[0] != 0 || consumed[1] != 0;
+            } else if (offsetInWindow != null) {
+                offsetInWindow[0] = 0;
+                offsetInWindow[1] = 0;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch a nested fling operation to the current nested scrolling parent.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if the parent consumed the nested fling
+     */
+    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,
+                    velocityY, consumed);
+        }
+        return false;
+    }
+
+    /**
+     * Dispatch a nested pre-fling operation to the current nested scrolling parent.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @return true if the parent consumed the nested fling
+     */
+    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX,
+                    velocityY);
+        }
+        return false;
+    }
+
+    /**
+     * View subclasses should always call this method on their
+     * <code>NestedScrollingChildHelper</code> when detached from a window.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     */
+    public void onDetachedFromWindow() {
+        ViewCompat.stopNestedScroll(mView);
+    }
+
+    /**
+     * Called when a nested scrolling child stops its current nested scroll operation.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
+     * method/{@link NestedScrollingChild} interface method with the same signature to implement
+     * the standard policy.</p>
+     *
+     * @param child Child view stopping its nested scroll. This may not be a direct child view.
+     */
+    public void onStopNestedScroll(View child) {
+        ViewCompat.stopNestedScroll(mView);
+    }
+}
diff --git a/v4/java/android/support/v4/view/NestedScrollingParent.java b/v4/java/android/support/v4/view/NestedScrollingParent.java
new file mode 100644
index 0000000..bc056b6
--- /dev/null
+++ b/v4/java/android/support/v4/view/NestedScrollingParent.java
@@ -0,0 +1,196 @@
+/*
+ * 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.v4.view;
+
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * This interface should be implemented by {@link android.view.ViewGroup ViewGroup} subclasses
+ * that wish to support scrolling operations delegated by a nested child view.
+ *
+ * <p>Classes implementing this interface should create a final instance of a
+ * {@link NestedScrollingParentHelper} as a field and delegate any View or ViewGroup methods
+ * to the <code>NestedScrollingParentHelper</code> methods of the same signature.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public interface NestedScrollingParent {
+    /**
+     * React to a descendant view initiating a nestable scroll operation, claiming the
+     * nested scroll operation if appropriate.
+     *
+     * <p>This method will be called in response to a descendant view invoking
+     * {@link ViewCompat#startNestedScroll(View, int)}. Each parent up the view hierarchy will be
+     * given an opportunity to respond and claim the nested scrolling operation by returning
+     * <code>true</code>.</p>
+     *
+     * <p>This method may be overridden by ViewParent implementations to indicate when the view
+     * is willing to support a nested scrolling operation that is about to begin. If it returns
+     * true, this ViewParent will become the target view's nested scrolling parent for the duration
+     * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+     * will receive a call to {@link #onStopNestedScroll(View)}.
+     * </p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
+     * @return true if this ViewParent accepts the nested scroll operation
+     */
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
+
+    /**
+     * React to the successful claiming of a nested scroll operation.
+     *
+     * <p>This method will be called after
+     * {@link #onStartNestedScroll(View, View, int) onStartNestedScroll} returns true. It offers
+     * an opportunity for the view and its superclasses to perform initial configuration
+     * for the nested scroll. Implementations of this method should always call their superclass's
+     * implementation of this method if one is present.</p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
+     * @see #onStartNestedScroll(View, View, int)
+     * @see #onStopNestedScroll(View)
+     */
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
+
+    /**
+     * React to a nested scroll operation ending.
+     *
+     * <p>Perform cleanup after a nested scrolling operation.
+     * This method will be called when a nested scroll stops, for example when a nested touch
+     * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+     * Implementations of this method should always call their superclass's implementation of this
+     * method if one is present.</p>
+     *
+     * @param target View that initiated the nested scroll
+     */
+    public void onStopNestedScroll(View target);
+
+    /**
+     * React to a nested scroll in progress.
+     *
+     * <p>This method will be called when the ViewParent's current nested scrolling child view
+     * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+     * previously returned <code>true</code> for a call to
+     * {@link #onStartNestedScroll(View, View, int)}.</p>
+     *
+     * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+     * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+     * position of multiple child elements, for example. The unconsumed portion may be used to
+     * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+     * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+     * scrolling content is reached.</p>
+     *
+     * @param target The descendent view controlling the nested scroll
+     * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+     * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+     * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+     */
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed);
+
+    /**
+     * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+     *
+     * <p>When working with nested scrolling often the parent view may want an opportunity
+     * to consume the scroll before the nested scrolling child does. An example of this is a
+     * drawer that contains a scrollable list. The user will want to be able to scroll the list
+     * fully into view before the list itself begins scrolling.</p>
+     *
+     * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+     * {@link View#dispatchNestedPreScroll(int, int, int[], int[])}. The implementation should
+     * report how any pixels of the scroll reported by dx, dy were consumed in the
+     * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+     * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+     * will always be 0.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+     */
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
+
+    /**
+     * Request a fling from a nested scroll.
+     *
+     * <p>This method signifies that a nested scrolling child has detected suitable conditions
+     * for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if this parent consumed or otherwise reacted to the fling
+     */
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
+
+    /**
+     * React to a nested fling before the target view consumes it.
+     *
+     * <p>This method siginfies that a nested scrolling child has detected a fling with the given
+     * velocity along each axis. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling parent is consuming motion as part of a
+     * {@link #onNestedPreScroll(View, int, int, int[]) pre-scroll}, it may be appropriate for
+     * it to also consume the pre-fling to complete that same motion. By returning
+     * <code>true</code> from this method, the parent indicates that the child should not
+     * fling its own internal content as well.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @return true if this parent consumed the fling ahead of the target view
+     */
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
+
+    /**
+     * Return the current axes of nested scrolling for this NestedScrollingParent.
+     *
+     * <p>A NestedScrollingParent returning something other than {@link ViewCompat#SCROLL_AXIS_NONE}
+     * is currently acting as a nested scrolling parent for one or more descendant views in
+     * the hierarchy.</p>
+     *
+     * @return Flags indicating the current axes of nested scrolling
+     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL
+     * @see ViewCompat#SCROLL_AXIS_VERTICAL
+     * @see ViewCompat#SCROLL_AXIS_NONE
+     */
+    public int getNestedScrollAxes();
+}
diff --git a/v4/java/android/support/v4/view/NestedScrollingParentHelper.java b/v4/java/android/support/v4/view/NestedScrollingParentHelper.java
new file mode 100644
index 0000000..2bccbec
--- /dev/null
+++ b/v4/java/android/support/v4/view/NestedScrollingParentHelper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.v4.view;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Helper class for implementing nested scrolling parent views compatible with Android platform
+ * versions earlier than Android 5.0 Lollipop (API 21).
+ *
+ * <p>{@link android.view.ViewGroup ViewGroup} subclasses should instantiate a final instance
+ * of this class as a field at construction. For each <code>ViewGroup</code> method that has
+ * a matching method signature in this class, delegate the operation to the helper instance
+ * in an overriden method implementation. This implements the standard framework policy
+ * for nested scrolling.</p>
+ *
+ * <p>Views invoking nested scrolling functionality should always do so from the relevant
+ * {@link ViewCompat}, {@link ViewGroupCompat} or {@link ViewParentCompat} compatibility
+ * shim static methods. This ensures interoperability with nested scrolling views on Android
+ * 5.0 Lollipop and newer.</p>
+ */
+public class NestedScrollingParentHelper {
+    private final ViewGroup mViewGroup;
+    private int mNestedScrollAxes;
+
+    /**
+     * Construct a new helper for a given ViewGroup
+     */
+    public NestedScrollingParentHelper(ViewGroup viewGroup) {
+        mViewGroup = viewGroup;
+    }
+
+    /**
+     * Called when a nested scrolling operation initiated by a descendant view is accepted
+     * by this ViewGroup.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.ViewGroup ViewGroup}
+     * subclass method/{@link NestedScrollingParent} interface method with the same signature
+     * to implement the standard policy.</p>
+     */
+    public void onNestedScrollAccepted(View child, View target, int axes) {
+        mNestedScrollAxes = axes;
+    }
+
+    /**
+     * Return the current axes of nested scrolling for this ViewGroup.
+     *
+     * <p>This is a delegate method. Call it from your {@link android.view.ViewGroup ViewGroup}
+     * subclass method/{@link NestedScrollingParent} interface method with the same signature
+     * to implement the standard policy.</p>
+     */
+    public int getNestedScrollAxes() {
+        return mNestedScrollAxes;
+    }
+}
diff --git a/v4/java/android/support/v4/view/ScrollingView.java b/v4/java/android/support/v4/view/ScrollingView.java
new file mode 100644
index 0000000..4fe0507
--- /dev/null
+++ b/v4/java/android/support/v4/view/ScrollingView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+/**
+ * An interface that can be implemented by Views to provide scroll related APIs.
+ */
+public interface ScrollingView {
+    /**
+     * <p>Compute the horizontal range that the horizontal scrollbar
+     * represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollExtent()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default range is the drawing width of this view.</p>
+     *
+     * @return the total horizontal range represented by the horizontal
+     *         scrollbar
+     *
+     * @see #computeHorizontalScrollExtent()
+     * @see #computeHorizontalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeHorizontalScrollRange();
+
+    /**
+     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the horizontal offset of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollExtent()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeHorizontalScrollOffset();
+
+    /**
+     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb
+     * within the horizontal range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeHorizontalScrollRange()} and
+     * {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing width of this view.</p>
+     *
+     * @return the horizontal extent of the scrollbar's thumb
+     *
+     * @see #computeHorizontalScrollRange()
+     * @see #computeHorizontalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeHorizontalScrollExtent();
+
+    /**
+     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollExtent()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * @return the total vertical range represented by the vertical scrollbar
+     *
+     * <p>The default range is the drawing height of this view.</p>
+     *
+     * @see #computeVerticalScrollExtent()
+     * @see #computeVerticalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeVerticalScrollRange();
+
+    /**
+     * <p>Compute the vertical offset of the vertical scrollbar's thumb
+     * within the horizontal range. This value is used to compute the position
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollRange()} and
+     * {@link #computeVerticalScrollExtent()}.</p>
+     *
+     * <p>The default offset is the scroll offset of this view.</p>
+     *
+     * @return the vertical offset of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollExtent()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeVerticalScrollOffset();
+
+    /**
+     * <p>Compute the vertical extent of the vertical scrollbar's thumb
+     * within the vertical range. This value is used to compute the length
+     * of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the
+     * units used by {@link #computeVerticalScrollRange()} and
+     * {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <p>The default extent is the drawing height of this view.</p>
+     *
+     * @return the vertical extent of the scrollbar's thumb
+     *
+     * @see #computeVerticalScrollRange()
+     * @see #computeVerticalScrollOffset()
+     * @see android.widget.ScrollBarDrawable
+     */
+    int computeVerticalScrollExtent();
+}
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index d883e00..3b2d54e 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -16,17 +16,23 @@
 
 package android.support.v4.view;
 
+import android.content.res.ColorStateList;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.IdRes;
 import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
 import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
@@ -253,6 +259,21 @@
      */
     public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
 
+    /**
+     * Indicates no axis of view scrolling.
+     */
+    public static final int SCROLL_AXIS_NONE = 0;
+
+    /**
+     * Indicates scrolling along the horizontal axis.
+     */
+    public static final int SCROLL_AXIS_HORIZONTAL = 1 << 0;
+
+    /**
+     * Indicates scrolling along the vertical axis.
+     */
+    public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
     interface ViewCompatImpl {
         public boolean canScrollHorizontally(View v, int direction);
         public boolean canScrollVertically(View v, int direction);
@@ -261,7 +282,7 @@
         public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
         public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
         public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
-        public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate);
+        public void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
         public boolean hasAccessibilityDelegate(View v);
         public boolean hasTransientState(View view);
         public void setHasTransientState(View view, boolean hasTransientState);
@@ -271,6 +292,7 @@
         public void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
         public int getImportantForAccessibility(View view);
         public void setImportantForAccessibility(View view, int mode);
+        public boolean isImportantForAccessibility(View view);
         public boolean performAccessibilityAction(View view, int action, Bundle arguments);
         public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
         public float getAlpha(View view);
@@ -330,8 +352,30 @@
         public void requestApplyInsets(View view);
         public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
         public boolean getFitsSystemWindows(View view);
+        void setFitsSystemWindows(View view, boolean fitSystemWindows);
         void jumpDrawablesToCurrentState(View v);
         void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener);
+        WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets);
+        WindowInsetsCompat dispatchApplyWindowInsets(View v, WindowInsetsCompat insets);
+        void setSaveFromParentEnabled(View view, boolean enabled);
+        void setActivated(View view, boolean activated);
+        boolean isPaddingRelative(View view);
+        ColorStateList getBackgroundTintList(View view);
+        void setBackgroundTintList(View view, ColorStateList tintList);
+        PorterDuff.Mode getBackgroundTintMode(View view);
+        void setBackgroundTintMode(View view, PorterDuff.Mode mode);
+        void setNestedScrollingEnabled(View view, boolean enabled);
+        boolean isNestedScrollingEnabled(View view);
+        boolean startNestedScroll(View view, int axes);
+        void stopNestedScroll(View view);
+        boolean hasNestedScrollingParent(View view);
+        boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed, int dxUnconsumed,
+                int dyUnconsumed, int[] offsetInWindow);
+        boolean dispatchNestedPreScroll(View view, int dx, int dy, int[] consumed,
+                int[] offsetInWindow);
+        boolean dispatchNestedFling(View view, float velocityX, float velocityY, boolean consumed);
+        boolean dispatchNestedPreFling(View view, float velocityX, float velocityY);
+        boolean isLaidOut(View view);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -342,10 +386,12 @@
 
 
         public boolean canScrollHorizontally(View v, int direction) {
-            return false;
+            return (v instanceof ScrollingView) &&
+                canScrollingViewScrollHorizontally((ScrollingView) v, direction);
         }
         public boolean canScrollVertically(View v, int direction) {
-            return false;
+            return (v instanceof ScrollingView) &&
+                    canScrollingViewScrollVertically((ScrollingView) v, direction);
         }
         public int getOverScrollMode(View v) {
             return OVER_SCROLL_NEVER;
@@ -399,6 +445,9 @@
         public void setImportantForAccessibility(View view, int mode) {
 
         }
+        public boolean isImportantForAccessibility(View view) {
+            return true;
+        }
         public boolean performAccessibilityAction(View view, int action, Bundle arguments) {
             return false;
         }
@@ -715,6 +764,11 @@
         }
 
         @Override
+        public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+            // noop
+        }
+
+        @Override
         public void jumpDrawablesToCurrentState(View view) {
             // Do nothing; API didn't exist.
         }
@@ -724,6 +778,154 @@
                 OnApplyWindowInsetsListener listener) {
             // noop
         }
+
+        @Override
+        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
+            return insets;
+        }
+
+        @Override
+        public WindowInsetsCompat dispatchApplyWindowInsets(View v, WindowInsetsCompat insets) {
+            return insets;
+        }
+
+        @Override
+        public void setSaveFromParentEnabled(View v, boolean enabled) {
+            // noop
+        }
+
+        @Override
+        public void setActivated(View view, boolean activated) {
+            // noop
+        }
+
+        @Override
+        public boolean isPaddingRelative(View view) {
+            return false;
+        }
+
+        public void setNestedScrollingEnabled(View view, boolean enabled) {
+            if (view instanceof NestedScrollingChild) {
+                ((NestedScrollingChild) view).setNestedScrollingEnabled(enabled);
+            }
+        }
+
+        @Override
+        public boolean isNestedScrollingEnabled(View view) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).isNestedScrollingEnabled();
+            }
+            return false;
+        }
+
+        @Override
+        public ColorStateList getBackgroundTintList(View view) {
+            return ViewCompatBase.getBackgroundTintList(view);
+        }
+
+        @Override
+        public void setBackgroundTintList(View view, ColorStateList tintList) {
+            ViewCompatBase.setBackgroundTintList(view, tintList);
+        }
+
+        @Override
+        public void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
+            ViewCompatBase.setBackgroundTintMode(view, mode);
+        }
+
+        @Override
+        public PorterDuff.Mode getBackgroundTintMode(View view) {
+            return ViewCompatBase.getBackgroundTintMode(view);
+        }
+
+        private boolean canScrollingViewScrollHorizontally(ScrollingView view, int direction) {
+            final int offset = view.computeHorizontalScrollOffset();
+            final int range = view.computeHorizontalScrollRange() -
+                    view.computeHorizontalScrollExtent();
+            if (range == 0) return false;
+            if (direction < 0) {
+                return offset > 0;
+            } else {
+                return offset < range - 1;
+            }
+        }
+
+        private boolean canScrollingViewScrollVertically(ScrollingView view, int direction) {
+            final int offset = view.computeVerticalScrollOffset();
+            final int range = view.computeVerticalScrollRange() -
+                    view.computeVerticalScrollExtent();
+            if (range == 0) return false;
+            if (direction < 0) {
+                return offset > 0;
+            } else {
+                return offset < range - 1;
+            }
+        }
+
+        public boolean startNestedScroll(View view, int axes) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).startNestedScroll(axes);
+            }
+            return false;
+        }
+
+        @Override
+        public void stopNestedScroll(View view) {
+            if (view instanceof NestedScrollingChild) {
+                ((NestedScrollingChild) view).stopNestedScroll();
+            }
+        }
+
+        @Override
+        public boolean hasNestedScrollingParent(View view) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).hasNestedScrollingParent();
+            }
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).dispatchNestedScroll(dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed, offsetInWindow);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedPreScroll(View view, int dx, int dy,
+                int[] consumed, int[] offsetInWindow) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).dispatchNestedPreScroll(dx, dy, consumed,
+                        offsetInWindow);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedFling(View view, float velocityX, float velocityY,
+                boolean consumed) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).dispatchNestedFling(velocityX, velocityY,
+                        consumed);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean dispatchNestedPreFling(View view, float velocityX, float velocityY) {
+            if (view instanceof NestedScrollingChild) {
+                return ((NestedScrollingChild) view).dispatchNestedPreFling(velocityX, velocityY);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean isLaidOut(View view) {
+            return ViewCompatBase.isLaidOut(view);
+        }
     }
 
     static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -893,6 +1095,16 @@
         public void jumpDrawablesToCurrentState(View view) {
             ViewCompatHC.jumpDrawablesToCurrentState(view);
         }
+
+        @Override
+        public void setSaveFromParentEnabled(View view, boolean enabled) {
+            ViewCompatHC.setSaveFromParentEnabled(view, enabled);
+        }
+
+        @Override
+        public void setActivated(View view, boolean activated) {
+            ViewCompatHC.setActivated(view, activated);
+        }
     }
 
     static class ICSViewCompatImpl extends HCViewCompatImpl {
@@ -919,8 +1131,10 @@
             ViewCompatICS.onInitializeAccessibilityNodeInfo(v, info.getInfo());
         }
         @Override
-        public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
-            ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge());
+        public void setAccessibilityDelegate(View v,
+                @Nullable AccessibilityDelegateCompat delegate) {
+            ViewCompatICS.setAccessibilityDelegate(v,
+                    delegate == null ? null : delegate.getBridge());
         }
 
         @Override
@@ -959,6 +1173,11 @@
             }
             return vpa;
         }
+
+        @Override
+        public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+            ViewCompatICS.setFitsSystemWindows(view, fitSystemWindows);
+        }
     }
 
     static class JBViewCompatImpl extends ICSViewCompatImpl {
@@ -1085,6 +1304,11 @@
         public int getWindowSystemUiVisibility(View view) {
             return ViewCompatJellybeanMr1.getWindowSystemUiVisibility(view);
         }
+
+        @Override
+        public boolean isPaddingRelative(View view) {
+            return ViewCompatJellybeanMr1.isPaddingRelative(view);
+        }
     }
 
     static class KitKatViewCompatImpl extends JbMr1ViewCompatImpl {
@@ -1102,47 +1326,137 @@
         public void setImportantForAccessibility(View view, int mode) {
             ViewCompatJB.setImportantForAccessibility(view, mode);
         }
+
+        @Override
+        public boolean isLaidOut(View view) {
+            return ViewCompatKitKat.isLaidOut(view);
+        }
     }
 
-    static class Api21ViewCompatImpl extends KitKatViewCompatImpl {
+    static class LollipopViewCompatImpl extends KitKatViewCompatImpl {
         @Override
         public void setTransitionName(View view, String transitionName) {
-            ViewCompatApi21.setTransitionName(view, transitionName);
+            ViewCompatLollipop.setTransitionName(view, transitionName);
         }
 
         @Override
         public String getTransitionName(View view) {
-            return ViewCompatApi21.getTransitionName(view);
+            return ViewCompatLollipop.getTransitionName(view);
         }
 
         @Override
         public void requestApplyInsets(View view) {
-            ViewCompatApi21.requestApplyInsets(view);
+            ViewCompatLollipop.requestApplyInsets(view);
         }
 
         @Override
         public void setElevation(View view, float elevation) {
-            ViewCompatApi21.setElevation(view, elevation);
+            ViewCompatLollipop.setElevation(view, elevation);
         }
 
         @Override
         public float getElevation(View view) {
-            return ViewCompatApi21.getElevation(view);
+            return ViewCompatLollipop.getElevation(view);
         }
 
         @Override
         public void setTranslationZ(View view, float translationZ) {
-            ViewCompatApi21.setTranslationZ(view, translationZ);
+            ViewCompatLollipop.setTranslationZ(view, translationZ);
         }
 
         @Override
         public float getTranslationZ(View view) {
-            return ViewCompatApi21.getTranslationZ(view);
+            return ViewCompatLollipop.getTranslationZ(view);
         }
 
         @Override
         public void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener) {
-            ViewCompatApi21.setOnApplyWindowInsetsListener(view, listener);
+            ViewCompatLollipop.setOnApplyWindowInsetsListener(view, listener);
+        }
+
+        @Override
+        public void setNestedScrollingEnabled(View view, boolean enabled) {
+            ViewCompatLollipop.setNestedScrollingEnabled(view, enabled);
+        }
+
+        @Override
+        public boolean isNestedScrollingEnabled(View view) {
+            return ViewCompatLollipop.isNestedScrollingEnabled(view);
+        }
+
+        @Override
+        public boolean startNestedScroll(View view, int axes) {
+            return ViewCompatLollipop.startNestedScroll(view, axes);
+        }
+
+        @Override
+        public void stopNestedScroll(View view) {
+            ViewCompatLollipop.stopNestedScroll(view);
+        }
+
+        @Override
+        public boolean hasNestedScrollingParent(View view) {
+            return ViewCompatLollipop.hasNestedScrollingParent(view);
+        }
+
+        @Override
+        public boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+            return ViewCompatLollipop.dispatchNestedScroll(view, dxConsumed, dyConsumed,
+                    dxUnconsumed, dyUnconsumed, offsetInWindow);
+        }
+
+        @Override
+        public boolean dispatchNestedPreScroll(View view, int dx, int dy,
+                int[] consumed, int[] offsetInWindow) {
+            return ViewCompatLollipop.dispatchNestedPreScroll(view, dx, dy, consumed,
+                    offsetInWindow);
+        }
+
+        @Override
+        public boolean dispatchNestedFling(View view, float velocityX, float velocityY,
+                boolean consumed) {
+            return ViewCompatLollipop.dispatchNestedFling(view, velocityX, velocityY, consumed);
+        }
+
+        @Override
+        public boolean dispatchNestedPreFling(View view, float velocityX, float velocityY) {
+            return ViewCompatLollipop.dispatchNestedPreFling(view, velocityX, velocityY);
+        }
+
+        @Override
+        public boolean isImportantForAccessibility(View view) {
+            return ViewCompatLollipop.isImportantForAccessibility(view);
+        }
+
+        @Override
+        public ColorStateList getBackgroundTintList(View view) {
+            return ViewCompatLollipop.getBackgroundTintList(view);
+        }
+
+        @Override
+        public void setBackgroundTintList(View view, ColorStateList tintList) {
+            ViewCompatLollipop.setBackgroundTintList(view, tintList);
+        }
+
+        @Override
+        public void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
+            ViewCompatLollipop.setBackgroundTintMode(view, mode);
+        }
+
+        @Override
+        public PorterDuff.Mode getBackgroundTintMode(View view) {
+            return ViewCompatLollipop.getBackgroundTintMode(view);
+        }
+
+        @Override
+        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
+            return ViewCompatLollipop.onApplyWindowInsets(v, insets);
+        }
+
+        @Override
+        public WindowInsetsCompat dispatchApplyWindowInsets(View v, WindowInsetsCompat insets) {
+            return ViewCompatLollipop.dispatchApplyWindowInsets(v, insets);
         }
     }
 
@@ -1150,7 +1464,7 @@
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
         if (version >= 21) {
-            IMPL = new Api21ViewCompatImpl();
+            IMPL = new LollipopViewCompatImpl();
         } else if (version >= 19) {
             IMPL = new KitKatViewCompatImpl();
         } else if (version >= 17) {
@@ -2243,6 +2557,16 @@
     }
 
     /**
+     * Sets whether or not this view should account for system screen decorations
+     * such as the status bar and inset its content; that is, controlling whether
+     * the default implementation of {@link View#fitSystemWindows(Rect)} will be
+     * executed. See that method for more details.
+     */
+    public static void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+        IMPL.setFitsSystemWindows(view, fitSystemWindows);
+    }
+
+    /**
      * On API 11 devices and above, call <code>Drawable.jumpToCurrentState()</code>
      * on all Drawable objects associated with this view.
      * <p>
@@ -2262,5 +2586,332 @@
         IMPL.setOnApplyWindowInsetsListener(v, listener);
     }
 
+    /**
+     * Called when the view should apply {@link WindowInsetsCompat} according to its internal policy.
+     *
+     * <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set
+     * it will be called during dispatch instead of this method. The listener may optionally
+     * call this method from its own implementation if it wishes to apply the view's default
+     * insets policy in addition to its own.</p>
+     *
+     * @param view The View against which to invoke the method.
+     * @param insets Insets to apply
+     * @return The supplied insets with any applied insets consumed
+     */
+    public static WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) {
+        return IMPL.onApplyWindowInsets(view, insets);
+    }
+
+    /**
+     * Request to apply the given window insets to this view or another view in its subtree.
+     *
+     * <p>This method should be called by clients wishing to apply insets corresponding to areas
+     * obscured by window decorations or overlays. This can include the status and navigation bars,
+     * action bars, input methods and more. New inset categories may be added in the future.
+     * The method returns the insets provided minus any that were applied by this view or its
+     * children.</p>
+     *
+     * @param insets Insets to apply
+     * @return The provided insets minus the insets that were consumed
+     */
+    public static WindowInsetsCompat dispatchApplyWindowInsets(View view,
+            WindowInsetsCompat insets) {
+        return IMPL.dispatchApplyWindowInsets(view, insets);
+    }
+
+    /**
+     * Controls whether the entire hierarchy under this view will save its
+     * state when a state saving traversal occurs from its parent.
+     *
+     * @param enabled Set to false to <em>disable</em> state saving, or true
+     * (the default) to allow it.
+     */
+    public static void setSaveFromParentEnabled(View v, boolean enabled) {
+        IMPL.setSaveFromParentEnabled(v, enabled);
+    }
+
+    /**
+     * Changes the activated state of this view. A view can be activated or not.
+     * Note that activation is not the same as selection.  Selection is
+     * a transient property, representing the view (hierarchy) the user is
+     * currently interacting with.  Activation is a longer-term state that the
+     * user can move views in and out of.
+     *
+     * @param activated true if the view must be activated, false otherwise
+     */
+    public static void setActivated(View view, boolean activated) {
+        IMPL.setActivated(view, activated);
+    }
+
+    /**
+     * Return if the padding as been set through relative values
+     * {@code View.setPaddingRelative(int, int, int, int)} or thru
+     *
+     * @return true if the padding is relative or false if it is not.
+     */
+    public static boolean isPaddingRelative(View view) {
+        return IMPL.isPaddingRelative(view);
+    }
+
+    /**
+     * Return the tint applied to the background drawable, if specified.
+     * <p>
+     * Only returns meaningful info when running on API v21 or newer, or if {@code view}
+     * implements the {@code TintableBackgroundView} interface.
+     */
+    public static ColorStateList getBackgroundTintList(View view) {
+        return IMPL.getBackgroundTintList(view);
+    }
+
+    /**
+     * Applies a tint to the background drawable.
+     * <p>
+     * This will always take effect when running on API v21 or newer. When running on platforms
+     * previous to API v21, it will only take effect if {@code view} implement the
+     * {@code TintableBackgroundView} interface.
+     */
+    public static void setBackgroundTintList(View view, ColorStateList tintList) {
+        IMPL.setBackgroundTintList(view, tintList);
+    }
+
+    /**
+     * Return the blending mode used to apply the tint to the background
+     * drawable, if specified.
+     * <p>
+     * Only returns meaningful info when running on API v21 or newer, or if {@code view}
+     * implements the {@code TintableBackgroundView} interface.
+     */
+    public static PorterDuff.Mode getBackgroundTintMode(View view) {
+        return IMPL.getBackgroundTintMode(view);
+    }
+
+    /**
+     * Specifies the blending mode used to apply the tint specified by
+     * {@link #setBackgroundTintList(android.view.View, android.content.res.ColorStateList)} to
+     * the background drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+     * <p>
+     * This will always take effect when running on API v21 or newer. When running on platforms
+     * previous to API v21, it will only take effect if {@code view} implement the
+     * {@code TintableBackgroundView} interface.
+     */
+    public static void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
+        IMPL.setBackgroundTintMode(view, mode);
+    }
     // TODO: getters for various view properties (rotation, etc)
+
+    /**
+     * Enable or disable nested scrolling for this view.
+     *
+     * <p>If this property is set to true the view will be permitted to initiate nested
+     * scrolling operations with a compatible parent view in the current hierarchy. If this
+     * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+     * while a nested scroll is in progress has the effect of
+     * {@link #stopNestedScroll(View) stopping} the nested scroll.</p>
+     *
+     * @param enabled true to enable nested scrolling, false to disable
+     *
+     * @see #isNestedScrollingEnabled(View)
+     */
+    public static void setNestedScrollingEnabled(View view, boolean enabled) {
+        IMPL.setNestedScrollingEnabled(view, enabled);
+    }
+
+    /**
+     * Returns true if nested scrolling is enabled for this view.
+     *
+     * <p>If nested scrolling is enabled and this View class implementation supports it,
+     * this view will act as a nested scrolling child view when applicable, forwarding data
+     * about the scroll operation in progress to a compatible and cooperating nested scrolling
+     * parent.</p>
+     *
+     * @return true if nested scrolling is enabled
+     *
+     * @see #setNestedScrollingEnabled(View, boolean)
+     */
+    public static boolean isNestedScrollingEnabled(View view) {
+        return IMPL.isNestedScrollingEnabled(view);
+    }
+
+    /**
+     * Begin a nestable scroll operation along the given axes.
+     *
+     * <p>A view starting a nested scroll promises to abide by the following contract:</p>
+     *
+     * <p>The view will call startNestedScroll upon initiating a scroll operation. In the case
+     * of a touch scroll this corresponds to the initial {@link MotionEvent#ACTION_DOWN}.
+     * In the case of touch scrolling the nested scroll will be terminated automatically in
+     * the same manner as {@link ViewParent#requestDisallowInterceptTouchEvent(boolean)}.
+     * In the event of programmatic scrolling the caller must explicitly call
+     * {@link #stopNestedScroll(View)} to indicate the end of the nested scroll.</p>
+     *
+     * <p>If <code>startNestedScroll</code> returns true, a cooperative parent was found.
+     * If it returns false the caller may ignore the rest of this contract until the next scroll.
+     * Calling startNestedScroll while a nested scroll is already in progress will return true.</p>
+     *
+     * <p>At each incremental step of the scroll the caller should invoke
+     * {@link #dispatchNestedPreScroll(View, int, int, int[], int[]) dispatchNestedPreScroll}
+     * once it has calculated the requested scrolling delta. If it returns true the nested scrolling
+     * parent at least partially consumed the scroll and the caller should adjust the amount it
+     * scrolls by.</p>
+     *
+     * <p>After applying the remainder of the scroll delta the caller should invoke
+     * {@link #dispatchNestedScroll(View, int, int, int, int, int[]) dispatchNestedScroll}, passing
+     * both the delta consumed and the delta unconsumed. A nested scrolling parent may treat
+     * these values differently. See
+     * {@link NestedScrollingParent#onNestedScroll(View, int, int, int, int)}.
+     * </p>
+     *
+     * @param axes Flags consisting of a combination of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}
+     *             and/or {@link ViewCompat#SCROLL_AXIS_VERTICAL}.
+     * @return true if a cooperative parent was found and nested scrolling has been enabled for
+     *         the current gesture.
+     *
+     * @see #stopNestedScroll(View)
+     * @see #dispatchNestedPreScroll(View, int, int, int[], int[])
+     * @see #dispatchNestedScroll(View, int, int, int, int, int[])
+     */
+    public static boolean startNestedScroll(View view, int axes) {
+        return IMPL.startNestedScroll(view, axes);
+    }
+
+    /**
+     * Stop a nested scroll in progress.
+     *
+     * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
+     *
+     * @see #startNestedScroll(View, int)
+     */
+    public static void stopNestedScroll(View view) {
+        IMPL.stopNestedScroll(view);
+    }
+
+    /**
+     * Returns true if this view has a nested scrolling parent.
+     *
+     * <p>The presence of a nested scrolling parent indicates that this view has initiated
+     * a nested scroll and it was accepted by an ancestor view further up the view hierarchy.</p>
+     *
+     * @return whether this view has a nested scrolling parent
+     */
+    public static boolean hasNestedScrollingParent(View view) {
+        return IMPL.hasNestedScrollingParent(view);
+    }
+
+    /**
+     * Dispatch one step of a nested scroll in progress.
+     *
+     * <p>Implementations of views that support nested scrolling should call this to report
+     * info about a scroll in progress to the current nested scrolling parent. If a nested scroll
+     * is not currently in progress or nested scrolling is not
+     * {@link #isNestedScrollingEnabled(View) enabled} for this view this method does nothing.</p>
+     *
+     * <p>Compatible View implementations should also call
+     * {@link #dispatchNestedPreScroll(View, int, int, int[], int[]) dispatchNestedPreScroll} before
+     * consuming a component of the scroll event themselves.</p>
+     *
+     * @param dxConsumed Horizontal distance in pixels consumed by this view during this scroll step
+     * @param dyConsumed Vertical distance in pixels consumed by this view during this scroll step
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param dyUnconsumed Horizontal scroll distance in pixels not consumed by this view
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the event was dispatched, false if it could not be dispatched.
+     * @see #dispatchNestedPreScroll(View, int, int, int[], int[])
+     */
+    public static boolean dispatchNestedScroll(View view, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+        return IMPL.dispatchNestedScroll(view, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                offsetInWindow);
+    }
+
+    /**
+     * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
+     *
+     * <p>Nested pre-scroll events are to nested scroll events what touch intercept is to touch.
+     * <code>dispatchNestedPreScroll</code> offers an opportunity for the parent view in a nested
+     * scrolling operation to consume some or all of the scroll operation before the child view
+     * consumes it.</p>
+     *
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. If not null, consumed[0] will contain the consumed component of dx
+     *                 and consumed[1] the consumed dy.
+     * @param offsetInWindow Optional. If not null, on return this will contain the offset
+     *                       in local view coordinates of this view from before this operation
+     *                       to after it completes. View implementations may use this to adjust
+     *                       expected input coordinate tracking.
+     * @return true if the parent consumed some or all of the scroll delta
+     * @see #dispatchNestedScroll(View, int, int, int, int, int[])
+     */
+    public static boolean dispatchNestedPreScroll(View view, int dx, int dy, int[] consumed,
+            int[] offsetInWindow) {
+        return IMPL.dispatchNestedPreScroll(view, dx, dy, consumed, offsetInWindow);
+    }
+
+    /**
+     * Dispatch a fling to a nested scrolling parent.
+     *
+     * <p>This method should be used to indicate that a nested scrolling child has detected
+     * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
+     */
+    public static boolean dispatchNestedFling(View view, float velocityX, float velocityY,
+            boolean consumed) {
+        return IMPL.dispatchNestedFling(view, velocityX, velocityY, consumed);
+    }
+
+    /**
+     * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+     *
+     * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+     * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+     * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+     * before the child view consumes it. If this method returns <code>true</code>, a nested
+     * parent view consumed the fling and this view should not scroll as a result.</p>
+     *
+     * <p>For a better user experience, only one view in a nested scrolling chain should consume
+     * the fling at a time. If a parent view consumed the fling this method will return false.
+     * Custom view implementations should account for this in two ways:</p>
+     *
+     * <ul>
+     *     <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+     *     call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+     *     position regardless.</li>
+     *     <li>If a nested parent does consume the fling, this view should not scroll at all,
+     *     even to settle back to a valid idle position.</li>
+     * </ul>
+     *
+     * <p>Views should also not offer fling velocities to nested parent views along an axis
+     * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+     * should not offer a horizontal fling velocity to its parents since scrolling along that
+     * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @return true if a nested scrolling parent consumed the fling
+     */
+    public static boolean dispatchNestedPreFling(View view, float velocityX, float velocityY) {
+        return IMPL.dispatchNestedPreFling(view, velocityX, velocityY);
+    }
+
+    /**
+     * Returns true if {@code view} has been through at least one layout since it
+     * was last attached to or detached from a window.
+     */
+    public static boolean isLaidOut(View view) {
+        return IMPL.isLaidOut(view);
+    }
 }
diff --git a/v4/java/android/support/v4/view/ViewGroupCompat.java b/v4/java/android/support/v4/view/ViewGroupCompat.java
index 9bf7046..2254c4a 100644
--- a/v4/java/android/support/v4/view/ViewGroupCompat.java
+++ b/v4/java/android/support/v4/view/ViewGroupCompat.java
@@ -44,14 +44,14 @@
     public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
 
     interface ViewGroupCompatImpl {
-        public boolean onRequestSendAccessibilityEvent(ViewGroup group, View child,
+        boolean onRequestSendAccessibilityEvent(ViewGroup group, View child,
                 AccessibilityEvent event);
-
-        public void setMotionEventSplittingEnabled(ViewGroup group, boolean split);
-        public int getLayoutMode(ViewGroup group);
-        public void setLayoutMode(ViewGroup group, int mode);
-        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup);
-        public boolean isTransitionGroup(ViewGroup group);
+        void setMotionEventSplittingEnabled(ViewGroup group, boolean split);
+        int getLayoutMode(ViewGroup group);
+        void setLayoutMode(ViewGroup group, int mode);
+        void setTransitionGroup(ViewGroup group, boolean isTransitionGroup);
+        boolean isTransitionGroup(ViewGroup group);
+        int getNestedScrollAxes(ViewGroup group);
     }
 
     static class ViewGroupCompatStubImpl implements ViewGroupCompatImpl {
@@ -82,6 +82,14 @@
         public boolean isTransitionGroup(ViewGroup group) {
             return false;
         }
+
+        @Override
+        public int getNestedScrollAxes(ViewGroup group) {
+            if (group instanceof NestedScrollingParent) {
+                return ((NestedScrollingParent) group).getNestedScrollAxes();
+            }
+            return ViewCompat.SCROLL_AXIS_NONE;
+        }
     }
 
     static class ViewGroupCompatHCImpl extends ViewGroupCompatStubImpl {
@@ -111,15 +119,20 @@
         }
     }
 
-    static class ViewGroupCompatApi21Impl extends ViewGroupCompatJellybeanMR2Impl {
+    static class ViewGroupCompatLollipopImpl extends ViewGroupCompatJellybeanMR2Impl {
         @Override
         public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-            ViewGroupCompatApi21.setTransitionGroup(group, isTransitionGroup);
+            ViewGroupCompatLollipop.setTransitionGroup(group, isTransitionGroup);
         }
 
         @Override
         public boolean isTransitionGroup(ViewGroup group) {
-            return ViewGroupCompatApi21.isTransitionGroup(group);
+            return ViewGroupCompatLollipop.isTransitionGroup(group);
+        }
+
+        @Override
+        public int getNestedScrollAxes(ViewGroup group) {
+            return ViewGroupCompatLollipop.getNestedScrollAxes(group);
         }
     }
 
@@ -127,7 +140,7 @@
     static {
         final int version = Build.VERSION.SDK_INT;
         if (version >= 21) {
-            IMPL = new ViewGroupCompatApi21Impl();
+            IMPL = new ViewGroupCompatLollipopImpl();
         } else if (version >= 18) {
             IMPL = new ViewGroupCompatJellybeanMR2Impl();
         } else if (version >= 14) {
@@ -235,4 +248,20 @@
     public static boolean isTransitionGroup(ViewGroup group) {
         return IMPL.isTransitionGroup(group);
     }
+
+    /**
+     * Return the current axes of nested scrolling for this ViewGroup.
+     *
+     * <p>A ViewGroup returning something other than {@link ViewCompat#SCROLL_AXIS_NONE} is
+     * currently acting as a nested scrolling parent for one or more descendant views in
+     * the hierarchy.</p>
+     *
+     * @return Flags indicating the current axes of nested scrolling
+     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL
+     * @see ViewCompat#SCROLL_AXIS_VERTICAL
+     * @see ViewCompat#SCROLL_AXIS_NONE
+     */
+    public static int getNestedScrollAxes(ViewGroup group) {
+        return IMPL.getNestedScrollAxes(group);
+    }
 }
diff --git a/v4/java/android/support/v4/view/ViewParentCompat.java b/v4/java/android/support/v4/view/ViewParentCompat.java
index 87cbb9b..b54a358 100644
--- a/v4/java/android/support/v4/view/ViewParentCompat.java
+++ b/v4/java/android/support/v4/view/ViewParentCompat.java
@@ -18,7 +18,10 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -32,6 +35,17 @@
     interface ViewParentCompatImpl {
         public boolean requestSendAccessibilityEvent(
                 ViewParent parent, View child, AccessibilityEvent event);
+        boolean onStartNestedScroll(ViewParent parent, View child, View target,
+                int nestedScrollAxes);
+        void onNestedScrollAccepted(ViewParent parent, View child, View target,
+                int nestedScrollAxes);
+        void onStopNestedScroll(ViewParent parent, View target);
+        void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed);
+        void onNestedPreScroll(ViewParent parent, View target, int dx, int dy, int[] consumed);
+        boolean onNestedFling(ViewParent parent, View target, float velocityX, float velocityY,
+                boolean consumed);
+        boolean onNestedPreFling(ViewParent parent, View target, float velocityX, float velocityY);
     }
 
     static class ViewParentCompatStubImpl implements ViewParentCompatImpl {
@@ -47,6 +61,69 @@
             manager.sendAccessibilityEvent(event);
             return true;
         }
+
+        @Override
+        public boolean onStartNestedScroll(ViewParent parent, View child, View target,
+                int nestedScrollAxes) {
+            if (parent instanceof NestedScrollingParent) {
+                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
+                        nestedScrollAxes);
+            }
+            return false;
+        }
+
+        @Override
+        public void onNestedScrollAccepted(ViewParent parent, View child, View target,
+                int nestedScrollAxes) {
+            if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedScrollAccepted(child, target,
+                        nestedScrollAxes);
+            }
+        }
+
+        @Override
+        public void onStopNestedScroll(ViewParent parent, View target) {
+            if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onStopNestedScroll(target);
+            }
+        }
+
+        @Override
+        public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed) {
+            if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed);
+            }
+        }
+
+        @Override
+        public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
+                int[] consumed) {
+            if (parent instanceof NestedScrollingParent) {
+                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
+            }
+        }
+
+        @Override
+        public boolean onNestedFling(ViewParent parent, View target, float velocityX,
+                float velocityY, boolean consumed) {
+            if (parent instanceof NestedScrollingParent) {
+                return ((NestedScrollingParent) parent).onNestedFling(target, velocityX, velocityY,
+                        consumed);
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
+                float velocityY) {
+            if (parent instanceof NestedScrollingParent) {
+                return ((NestedScrollingParent) parent).onNestedPreFling(target, velocityX,
+                        velocityY);
+            }
+            return false;
+        }
     }
 
     static class ViewParentCompatICSImpl extends ViewParentCompatStubImpl {
@@ -57,10 +134,59 @@
         }
     }
 
+    static class ViewParentCompatLollipopImpl extends ViewParentCompatICSImpl {
+        @Override
+        public boolean onStartNestedScroll(ViewParent parent, View child, View target,
+                int nestedScrollAxes) {
+            return ViewParentCompatLollipop.onStartNestedScroll(parent, child, target,
+                    nestedScrollAxes);
+        }
+
+        @Override
+        public void onNestedScrollAccepted(ViewParent parent, View child, View target,
+                int nestedScrollAxes) {
+            ViewParentCompatLollipop.onNestedScrollAccepted(parent, child, target,
+                    nestedScrollAxes);
+        }
+
+        @Override
+        public void onStopNestedScroll(ViewParent parent, View target) {
+            ViewParentCompatLollipop.onStopNestedScroll(parent, target);
+        }
+
+        @Override
+        public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed) {
+            ViewParentCompatLollipop.onNestedScroll(parent, target, dxConsumed, dyConsumed,
+                    dxUnconsumed, dyUnconsumed);
+        }
+
+        @Override
+        public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
+                int[] consumed) {
+            ViewParentCompatLollipop.onNestedPreScroll(parent, target, dx, dy, consumed);
+        }
+
+        @Override
+        public boolean onNestedFling(ViewParent parent, View target, float velocityX,
+                float velocityY, boolean consumed) {
+            return ViewParentCompatLollipop.onNestedFling(parent, target, velocityX, velocityY,
+                    consumed);
+        }
+
+        @Override
+        public boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
+                float velocityY) {
+            return ViewParentCompatLollipop.onNestedPreFling(parent, target, velocityX, velocityY);
+        }
+    }
+
     static final ViewParentCompatImpl IMPL;
     static {
         final int version = Build.VERSION.SDK_INT;
-        if (version >= 14) {
+        if (version >= 21) {
+            IMPL = new ViewParentCompatLollipopImpl();
+        } else if (version >= 14) {
             IMPL = new ViewParentCompatICSImpl();
         } else {
             IMPL = new ViewParentCompatStubImpl();
@@ -95,4 +221,168 @@
             ViewParent parent, View child, AccessibilityEvent event) {
         return IMPL.requestSendAccessibilityEvent(parent, child, event);
     }
+
+    /**
+     * React to a descendant view initiating a nestable scroll operation, claiming the
+     * nested scroll operation if appropriate.
+     *
+     * <p>This method will be called in response to a descendant view invoking
+     * {@link ViewCompat#startNestedScroll(View, int)}. Each parent up the view hierarchy will be
+     * given an opportunity to respond and claim the nested scrolling operation by returning
+     * <code>true</code>.</p>
+     *
+     * <p>This method may be overridden by ViewParent implementations to indicate when the view
+     * is willing to support a nested scrolling operation that is about to begin. If it returns
+     * true, this ViewParent will become the target view's nested scrolling parent for the duration
+     * of the scroll operation in progress. When the nested scroll is finished this ViewParent
+     * will receive a call to {@link #onStopNestedScroll(ViewParent, View)}.
+     * </p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
+     * @return true if this ViewParent accepts the nested scroll operation
+     */
+    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
+            int nestedScrollAxes) {
+        return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
+    }
+
+    /**
+     * React to the successful claiming of a nested scroll operation.
+     *
+     * <p>This method will be called after
+     * {@link #onStartNestedScroll(ViewParent, View, View, int) onStartNestedScroll} returns true.
+     * It offers an opportunity for the view and its superclasses to perform initial configuration
+     * for the nested scroll. Implementations of this method should always call their superclass's
+     * implementation of this method if one is present.</p>
+     *
+     * @param child Direct child of this ViewParent containing target
+     * @param target View that initiated the nested scroll
+     * @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+     *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
+     * @see #onStartNestedScroll(ViewParent, View, View, int)
+     * @see #onStopNestedScroll(ViewParent, View)
+     */
+    public static void onNestedScrollAccepted(ViewParent parent, View child, View target,
+            int nestedScrollAxes) {
+        IMPL.onNestedScrollAccepted(parent, child, target, nestedScrollAxes);
+    }
+
+    /**
+     * React to a nested scroll operation ending.
+     *
+     * <p>Perform cleanup after a nested scrolling operation.
+     * This method will be called when a nested scroll stops, for example when a nested touch
+     * scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
+     * Implementations of this method should always call their superclass's implementation of this
+     * method if one is present.</p>
+     *
+     * @param target View that initiated the nested scroll
+     */
+    public static void onStopNestedScroll(ViewParent parent, View target) {
+        IMPL.onStopNestedScroll(parent, target);
+    }
+
+    /**
+     * React to a nested scroll in progress.
+     *
+     * <p>This method will be called when the ViewParent's current nested scrolling child view
+     * dispatches a nested scroll event. To receive calls to this method the ViewParent must have
+     * previously returned <code>true</code> for a call to
+     * {@link #onStartNestedScroll(ViewParent, View, View, int)}.</p>
+     *
+     * <p>Both the consumed and unconsumed portions of the scroll distance are reported to the
+     * ViewParent. An implementation may choose to use the consumed portion to match or chase scroll
+     * position of multiple child elements, for example. The unconsumed portion may be used to
+     * allow continuous dragging of multiple scrolling or draggable elements, such as scrolling
+     * a list within a vertical drawer where the drawer begins dragging once the edge of inner
+     * scrolling content is reached.</p>
+     *
+     * @param target The descendent view controlling the nested scroll
+     * @param dxConsumed Horizontal scroll distance in pixels already consumed by target
+     * @param dyConsumed Vertical scroll distance in pixels already consumed by target
+     * @param dxUnconsumed Horizontal scroll distance in pixels not consumed by target
+     * @param dyUnconsumed Vertical scroll distance in pixels not consumed by target
+     */
+    public static void onNestedScroll(ViewParent parent, View target, int dxConsumed,
+            int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+        IMPL.onNestedScroll(parent, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
+    }
+
+    /**
+     * React to a nested scroll in progress before the target view consumes a portion of the scroll.
+     *
+     * <p>When working with nested scrolling often the parent view may want an opportunity
+     * to consume the scroll before the nested scrolling child does. An example of this is a
+     * drawer that contains a scrollable list. The user will want to be able to scroll the list
+     * fully into view before the list itself begins scrolling.</p>
+     *
+     * <p><code>onNestedPreScroll</code> is called when a nested scrolling child invokes
+     * {@link ViewCompat#dispatchNestedPreScroll(View, int, int, int[], int[])}. The implementation
+     * should report how any pixels of the scroll reported by dx, dy were consumed in the
+     * <code>consumed</code> array. Index 0 corresponds to dx and index 1 corresponds to dy.
+     * This parameter will never be null. Initial values for consumed[0] and consumed[1]
+     * will always be 0.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param dx Horizontal scroll distance in pixels
+     * @param dy Vertical scroll distance in pixels
+     * @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
+     */
+    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
+            int[] consumed) {
+        IMPL.onNestedPreScroll(parent, target, dx, dy, consumed);
+    }
+
+    /**
+     * Request a fling from a nested scroll.
+     *
+     * <p>This method signifies that a nested scrolling child has detected suitable conditions
+     * for a fling. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling child view would normally fling but it is at the edge of
+     * its own content, it can use this method to delegate the fling to its nested scrolling
+     * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @param consumed true if the child consumed the fling, false otherwise
+     * @return true if this parent consumed or otherwise reacted to the fling
+     */
+    public static boolean onNestedFling(ViewParent parent, View target, float velocityX,
+            float velocityY, boolean consumed) {
+        return IMPL.onNestedFling(parent, target, velocityX, velocityY, consumed);
+    }
+
+    /**
+     * React to a nested fling before the target view consumes it.
+     *
+     * <p>This method siginfies that a nested scrolling child has detected a fling with the given
+     * velocity along each axis. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling parent is consuming motion as part of a
+     * {@link #onNestedPreScroll(ViewParent, View, int, int, int[]) pre-scroll}, it may be
+     * appropriate for it to also consume the pre-fling to complete that same motion. By returning
+     * <code>true</code> from this method, the parent indicates that the child should not
+     * fling its own internal content as well.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @return true if this parent consumed the fling ahead of the target view
+     */
+    public static boolean onNestedPreFling(ViewParent parent, View target, float velocityX,
+            float velocityY) {
+        return IMPL.onNestedPreFling(parent, target, velocityX, velocityY);
+    }
+
 }
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
index 0acc4b9..8813929 100644
--- a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
@@ -300,7 +300,10 @@
 
             @Override
             public void run() {
-                startAnimation(mVpa, mViewRef.get());
+                final View view = mViewRef.get();
+                if (view != null) {
+                    startAnimation(mVpa, view);
+                }
             }
         };
 
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 88bd985..055598f 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -36,6 +36,16 @@
     public static class AccessibilityActionCompat {
         private final Object mAction;
 
+        /**
+         * Creates a new instance.
+         *
+         * @param actionId The action id.
+         * @param label The action label.
+         */
+        public AccessibilityActionCompat(int actionId, CharSequence label) {
+            this(IMPL.newAccessibilityAction(actionId, label));
+        }
+
         private AccessibilityActionCompat(Object action) {
             mAction = action;
         }
@@ -46,7 +56,7 @@
          * @return The action id.
          */
         public int getId() {
-            return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getId(mAction);
+            return IMPL.getAccessibilityActionId(mAction);
         }
 
         /**
@@ -56,7 +66,7 @@
          * @return The label.
          */
         public CharSequence getLabel() {
-            return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getLabel(mAction);
+            return IMPL.getAccessibilityActionLabel(mAction);
         }
     }
 
@@ -176,6 +186,7 @@
     }
 
     static interface AccessibilityNodeInfoImpl {
+        public Object newAccessibilityAction(int actionId, CharSequence label);
         public Object obtain();
         public Object obtain(View source);
         public Object obtain(Object info);
@@ -191,6 +202,9 @@
         public void addChild(Object info, View child, int virtualDescendantId);
         public int getActions(Object info);
         public void addAction(Object info, int action);
+        public void addAction(Object info, Object action);
+        public int getAccessibilityActionId(Object action);
+        public CharSequence getAccessibilityActionLabel(Object action);
         public boolean performAction(Object info, int action);
         public boolean performAction(Object info, int action, Bundle arguments);
         public void setMovementGranularities(Object info, int granularities);
@@ -246,7 +260,6 @@
         public void setCollectionItemInfo(Object info, Object collectionItemInfo);
         public Object getRangeInfo(Object info);
         public List<Object> getActionList(Object info);
-        public void addAction(Object info, int id, CharSequence label);
         public Object obtainCollectionInfo(int rowCount, int columnCount, boolean hierarchical,
                 int selectionMode);
         public int getCollectionInfoColumnCount(Object info);
@@ -260,10 +273,21 @@
         public int getCollectionItemRowSpan(Object info);
         public boolean isCollectionItemHeading(Object info);
         public boolean isCollectionItemSelected(Object info);
+        public AccessibilityNodeInfoCompat getTraversalBefore(Object info);
+        public void setTraversalBefore(Object info, View view);
+        public void setTraversalBefore(Object info, View root, int virtualDescendantId);
+        public AccessibilityNodeInfoCompat getTraversalAfter(Object info);
+        public void setTraversalAfter(Object info, View view);
+        public void setTraversalAfter(Object info, View root, int virtualDescendantId);
     }
 
     static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
         @Override
+        public Object newAccessibilityAction(int actionId, CharSequence label) {
+            return null;
+        }
+
+        @Override
         public Object obtain() {
             return null;
         }
@@ -289,6 +313,21 @@
         }
 
         @Override
+        public void addAction(Object info, Object action) {
+
+        }
+
+        @Override
+        public int getAccessibilityActionId(Object action) {
+            return 0;
+        }
+
+        @Override
+        public CharSequence getAccessibilityActionLabel(Object action) {
+            return null;
+        }
+
+        @Override
         public void addChild(Object info, View child) {
 
         }
@@ -612,10 +651,6 @@
         }
 
         @Override
-        public void addAction(Object info, int id, CharSequence label) {
-        }
-
-        @Override
         public Object obtainCollectionInfo(int rowCount, int columnCount, boolean hierarchical,
                 int selectionMode) {
             return null;
@@ -671,6 +706,32 @@
         public boolean isCollectionItemSelected(Object info) {
             return false;
         }
+
+        @Override
+        public AccessibilityNodeInfoCompat getTraversalBefore(Object info) {
+            return null;
+        }
+
+        @Override
+        public void setTraversalBefore(Object info, View view) {
+        }
+
+        @Override
+        public void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+        }
+
+        @Override
+        public AccessibilityNodeInfoCompat getTraversalAfter(Object info) {
+            return null;
+        }
+
+        @Override
+        public void setTraversalAfter(Object info, View view) {
+        }
+
+        @Override
+        public void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+        }
     }
 
     static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -908,13 +969,6 @@
         public void recycle(Object info) {
             AccessibilityNodeInfoCompatIcs.recycle(info);
         }
-
-        @Override
-        public void addAction(Object info, int id, CharSequence label) {
-            if (Integer.bitCount(id) == 1) {
-                addAction(info, id);
-            }
-        }
     }
 
     static class AccessibilityNodeInfoJellybeanImpl extends AccessibilityNodeInfoIcsImpl {
@@ -1090,6 +1144,11 @@
 
     static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
         @Override
+        public Object newAccessibilityAction(int actionId, CharSequence label) {
+            return AccessibilityNodeInfoCompatApi21.newAccessibilityAction(actionId, label);
+        }
+
+        @Override
         public List<Object> getActionList(Object info) {
             return AccessibilityNodeInfoCompatApi21.getActionList(info);
         }
@@ -1102,8 +1161,18 @@
         }
 
         @Override
-        public void addAction(Object info, int id, CharSequence label) {
-            AccessibilityNodeInfoCompatApi21.addAction(info, id, label);
+        public void addAction(Object info, Object action) {
+            AccessibilityNodeInfoCompatApi21.addAction(info, action);
+        }
+
+        @Override
+        public int getAccessibilityActionId(Object action) {
+            return AccessibilityNodeInfoCompatApi21.getAccessibilityActionId(action);
+        }
+
+        @Override
+        public CharSequence getAccessibilityActionLabel(Object action) {
+            return AccessibilityNodeInfoCompatApi21.getAccessibilityActionLabel(action);
         }
 
         @Override
@@ -1119,8 +1188,52 @@
         }
     }
 
+    static class AccessibilityNodeInfoApi22Impl extends AccessibilityNodeInfoApi21Impl {
+        @Override
+        public AccessibilityNodeInfoCompat getTraversalBefore(Object info) {
+            Object nodeInfo = AccessibilityNodeInfoCompatApi22.getTraversalBefore(info);
+            if (nodeInfo == null) {
+                return null;
+            }
+
+            return new AccessibilityNodeInfoCompat(nodeInfo);
+        }
+
+        @Override
+        public void setTraversalBefore(Object info, View view) {
+            AccessibilityNodeInfoCompatApi22.setTraversalBefore(info, view);
+        }
+
+        @Override
+        public void setTraversalBefore(Object info, View root, int virtualDescendantId) {
+            AccessibilityNodeInfoCompatApi22.setTraversalBefore(info, root, virtualDescendantId);
+        }
+
+        @Override
+        public AccessibilityNodeInfoCompat getTraversalAfter(Object info) {
+            Object nodeInfo = AccessibilityNodeInfoCompatApi22.getTraversalAfter(info);
+            if (nodeInfo == null) {
+                return null;
+            }
+
+            return new AccessibilityNodeInfoCompat(nodeInfo);
+        }
+
+        @Override
+        public void setTraversalAfter(Object info, View view) {
+            AccessibilityNodeInfoCompatApi22.setTraversalAfter(info, view);
+        }
+
+        @Override
+        public void setTraversalAfter(Object info, View root, int virtualDescendantId) {
+            AccessibilityNodeInfoCompatApi22.setTraversalAfter(info, root, virtualDescendantId);
+        }
+    }
+
     static {
-        if (Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 22) {
+            IMPL = new AccessibilityNodeInfoApi22Impl();
+        } else if (Build.VERSION.SDK_INT >= 21) {
             IMPL = new AccessibilityNodeInfoApi21Impl();
         } else if (Build.VERSION.SDK_INT >= 19) { // KitKat
             IMPL = new AccessibilityNodeInfoKitKatImpl();
@@ -1693,6 +1806,21 @@
     }
 
     /**
+     * Adds an action that can be performed on the 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 action The action.
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void addAction(AccessibilityActionCompat action) {
+        IMPL.addAction(mInfo, action.mAction);
+    }
+
+    /**
      * Performs an action on the node.
      * <p>
      * <strong>Note:</strong> An action can be performed only if the request is
@@ -2389,14 +2517,18 @@
      * @return A list of AccessibilityActions.
      */
     public List<AccessibilityActionCompat> getActionList() {
-        List<AccessibilityActionCompat> result = new ArrayList<AccessibilityActionCompat>();
         List<Object> actions = IMPL.getActionList(mInfo);
-        final int actionCount = actions.size();
-        for (int i = 0; i < actionCount; i++) {
-            Object action = actions.get(i);
-            result.add(new AccessibilityActionCompat(action));
+        if (actions != null) {
+            List<AccessibilityActionCompat> result = new ArrayList<AccessibilityActionCompat>();
+            final int actionCount = actions.size();
+            for (int i = 0; i < actionCount; i++) {
+                Object action = actions.get(i);
+                result.add(new AccessibilityActionCompat(action));
+            }
+            return result;
+        } else {
+            return Collections.<AccessibilityActionCompat>emptyList();
         }
-        return result;
     }
 
 
diff --git a/v4/java/android/support/v4/view/animation/FastOutLinearInInterpolator.java b/v4/java/android/support/v4/view/animation/FastOutLinearInInterpolator.java
new file mode 100644
index 0000000..1f39aa8
--- /dev/null
+++ b/v4/java/android/support/v4/view/animation/FastOutLinearInInterpolator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.v4.view.animation;
+
+/**
+ * Interpolator corresponding to {@link android.R.interpolator#fast_out_linear_in}.
+ *
+ * Uses a lookup table for the Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0, 0)
+ * P1 (0.4, 0)
+ * P2 (1.0, 1.0)
+ * P3 (1.0, 1.0)
+ */
+public class FastOutLinearInInterpolator extends LookupTableInterpolator {
+
+    /**
+     * Lookup table values sampled with x at regular intervals between 0 and 1 for a total of
+     * 201 points.
+     */
+    private static final float[] VALUES = new float[] {
+            0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0008f, 0.0013f, 0.0018f,
+            0.0024f, 0.0032f, 0.0040f, 0.0049f, 0.0059f, 0.0069f, 0.0081f,
+            0.0093f, 0.0106f, 0.0120f, 0.0135f, 0.0151f, 0.0167f, 0.0184f,
+            0.0201f, 0.0220f, 0.0239f, 0.0259f, 0.0279f, 0.0300f, 0.0322f,
+            0.0345f, 0.0368f, 0.0391f, 0.0416f, 0.0441f, 0.0466f, 0.0492f,
+            0.0519f, 0.0547f, 0.0574f, 0.0603f, 0.0632f, 0.0662f, 0.0692f,
+            0.0722f, 0.0754f, 0.0785f, 0.0817f, 0.0850f, 0.0884f, 0.0917f,
+            0.0952f, 0.0986f, 0.1021f, 0.1057f, 0.1093f, 0.1130f, 0.1167f,
+            0.1205f, 0.1243f, 0.1281f, 0.1320f, 0.1359f, 0.1399f, 0.1439f,
+            0.1480f, 0.1521f, 0.1562f, 0.1604f, 0.1647f, 0.1689f, 0.1732f,
+            0.1776f, 0.1820f, 0.1864f, 0.1909f, 0.1954f, 0.1999f, 0.2045f,
+            0.2091f, 0.2138f, 0.2184f, 0.2232f, 0.2279f, 0.2327f, 0.2376f,
+            0.2424f, 0.2473f, 0.2523f, 0.2572f, 0.2622f, 0.2673f, 0.2723f,
+            0.2774f, 0.2826f, 0.2877f, 0.2929f, 0.2982f, 0.3034f, 0.3087f,
+            0.3141f, 0.3194f, 0.3248f, 0.3302f, 0.3357f, 0.3412f, 0.3467f,
+            0.3522f, 0.3578f, 0.3634f, 0.3690f, 0.3747f, 0.3804f, 0.3861f,
+            0.3918f, 0.3976f, 0.4034f, 0.4092f, 0.4151f, 0.4210f, 0.4269f,
+            0.4329f, 0.4388f, 0.4448f, 0.4508f, 0.4569f, 0.4630f, 0.4691f,
+            0.4752f, 0.4814f, 0.4876f, 0.4938f, 0.5000f, 0.5063f, 0.5126f,
+            0.5189f, 0.5252f, 0.5316f, 0.5380f, 0.5444f, 0.5508f, 0.5573f,
+            0.5638f, 0.5703f, 0.5768f, 0.5834f, 0.5900f, 0.5966f, 0.6033f,
+            0.6099f, 0.6166f, 0.6233f, 0.6301f, 0.6369f, 0.6436f, 0.6505f,
+            0.6573f, 0.6642f, 0.6710f, 0.6780f, 0.6849f, 0.6919f, 0.6988f,
+            0.7059f, 0.7129f, 0.7199f, 0.7270f, 0.7341f, 0.7413f, 0.7484f,
+            0.7556f, 0.7628f, 0.7700f, 0.7773f, 0.7846f, 0.7919f, 0.7992f,
+            0.8066f, 0.8140f, 0.8214f, 0.8288f, 0.8363f, 0.8437f, 0.8513f,
+            0.8588f, 0.8664f, 0.8740f, 0.8816f, 0.8892f, 0.8969f, 0.9046f,
+            0.9124f, 0.9201f, 0.9280f, 0.9358f, 0.9437f, 0.9516f, 0.9595f,
+            0.9675f, 0.9755f, 0.9836f, 0.9918f, 1.0000f
+    };
+
+    public FastOutLinearInInterpolator() {
+        super(VALUES);
+    }
+}
diff --git a/v4/java/android/support/v4/view/animation/FastOutSlowInInterpolator.java b/v4/java/android/support/v4/view/animation/FastOutSlowInInterpolator.java
new file mode 100644
index 0000000..a21d131
--- /dev/null
+++ b/v4/java/android/support/v4/view/animation/FastOutSlowInInterpolator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.v4.view.animation;
+
+/**
+ * Interpolator corresponding to {@link android.R.interpolator#fast_out_slow_in}.
+ *
+ * Uses a lookup table for the Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0, 0)
+ * P1 (0.4, 0)
+ * P2 (0.2, 1.0)
+ * P3 (1.0, 1.0)
+ */
+public class FastOutSlowInInterpolator extends LookupTableInterpolator {
+
+    /**
+     * Lookup table values sampled with x at regular intervals between 0 and 1 for a total of
+     * 201 points.
+     */
+    private static final float[] VALUES = new float[] {
+            0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f,
+            0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f,
+            0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f,
+            0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f,
+            0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f,
+            0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f,
+            0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f,
+            0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f,
+            0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f,
+            0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f,
+            0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f,
+            0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f,
+            0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f,
+            0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f,
+            0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f,
+            0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f,
+            0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f,
+            0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f,
+            0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f,
+            0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f,
+            0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f,
+            0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f,
+            0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f,
+            0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f,
+            0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f,
+            0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f,
+            0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f,
+            0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f,
+            0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f
+    };
+
+    public FastOutSlowInInterpolator() {
+        super(VALUES);
+    }
+
+}
diff --git a/v4/java/android/support/v4/view/animation/LinearOutSlowInInterpolator.java b/v4/java/android/support/v4/view/animation/LinearOutSlowInInterpolator.java
new file mode 100644
index 0000000..41f4cd6
--- /dev/null
+++ b/v4/java/android/support/v4/view/animation/LinearOutSlowInInterpolator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.v4.view.animation;
+
+/**
+ * Interpolator corresponding to {@link android.R.interpolator#linear_out_slow_in}.
+ *
+ * Uses a lookup table for the Bezier curve from (0,0) to (1,1) with control points:
+ * P0 (0, 0)
+ * P1 (0, 0)
+ * P2 (0.2, 1.0)
+ * P3 (1.0, 1.0)
+ */
+public class LinearOutSlowInInterpolator extends LookupTableInterpolator {
+
+    /**
+     * Lookup table values sampled with x at regular intervals between 0 and 1 for a total of
+     * 201 points.
+     */
+    private static final float[] VALUES = new float[] {
+            0.0000f, 0.0222f, 0.0424f, 0.0613f, 0.0793f, 0.0966f, 0.1132f,
+            0.1293f, 0.1449f, 0.1600f, 0.1747f, 0.1890f, 0.2029f, 0.2165f,
+            0.2298f, 0.2428f, 0.2555f, 0.2680f, 0.2802f, 0.2921f, 0.3038f,
+            0.3153f, 0.3266f, 0.3377f, 0.3486f, 0.3592f, 0.3697f, 0.3801f,
+            0.3902f, 0.4002f, 0.4100f, 0.4196f, 0.4291f, 0.4385f, 0.4477f,
+            0.4567f, 0.4656f, 0.4744f, 0.4831f, 0.4916f, 0.5000f, 0.5083f,
+            0.5164f, 0.5245f, 0.5324f, 0.5402f, 0.5479f, 0.5555f, 0.5629f,
+            0.5703f, 0.5776f, 0.5847f, 0.5918f, 0.5988f, 0.6057f, 0.6124f,
+            0.6191f, 0.6257f, 0.6322f, 0.6387f, 0.6450f, 0.6512f, 0.6574f,
+            0.6635f, 0.6695f, 0.6754f, 0.6812f, 0.6870f, 0.6927f, 0.6983f,
+            0.7038f, 0.7093f, 0.7147f, 0.7200f, 0.7252f, 0.7304f, 0.7355f,
+            0.7406f, 0.7455f, 0.7504f, 0.7553f, 0.7600f, 0.7647f, 0.7694f,
+            0.7740f, 0.7785f, 0.7829f, 0.7873f, 0.7917f, 0.7959f, 0.8002f,
+            0.8043f, 0.8084f, 0.8125f, 0.8165f, 0.8204f, 0.8243f, 0.8281f,
+            0.8319f, 0.8356f, 0.8392f, 0.8429f, 0.8464f, 0.8499f, 0.8534f,
+            0.8568f, 0.8601f, 0.8634f, 0.8667f, 0.8699f, 0.8731f, 0.8762f,
+            0.8792f, 0.8823f, 0.8852f, 0.8882f, 0.8910f, 0.8939f, 0.8967f,
+            0.8994f, 0.9021f, 0.9048f, 0.9074f, 0.9100f, 0.9125f, 0.9150f,
+            0.9174f, 0.9198f, 0.9222f, 0.9245f, 0.9268f, 0.9290f, 0.9312f,
+            0.9334f, 0.9355f, 0.9376f, 0.9396f, 0.9416f, 0.9436f, 0.9455f,
+            0.9474f, 0.9492f, 0.9510f, 0.9528f, 0.9545f, 0.9562f, 0.9579f,
+            0.9595f, 0.9611f, 0.9627f, 0.9642f, 0.9657f, 0.9672f, 0.9686f,
+            0.9700f, 0.9713f, 0.9726f, 0.9739f, 0.9752f, 0.9764f, 0.9776f,
+            0.9787f, 0.9798f, 0.9809f, 0.9820f, 0.9830f, 0.9840f, 0.9849f,
+            0.9859f, 0.9868f, 0.9876f, 0.9885f, 0.9893f, 0.9900f, 0.9908f,
+            0.9915f, 0.9922f, 0.9928f, 0.9934f, 0.9940f, 0.9946f, 0.9951f,
+            0.9956f, 0.9961f, 0.9966f, 0.9970f, 0.9974f, 0.9977f, 0.9981f,
+            0.9984f, 0.9987f, 0.9989f, 0.9992f, 0.9994f, 0.9995f, 0.9997f,
+            0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f
+    };
+
+    public LinearOutSlowInInterpolator() {
+        super(VALUES);
+    }
+
+}
diff --git a/v4/java/android/support/v4/view/animation/LookupTableInterpolator.java b/v4/java/android/support/v4/view/animation/LookupTableInterpolator.java
new file mode 100644
index 0000000..c234177
--- /dev/null
+++ b/v4/java/android/support/v4/view/animation/LookupTableInterpolator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An {@link Interpolator} that uses a lookup table to compute an interpolation based on a
+ * given input.
+ */
+abstract class LookupTableInterpolator implements Interpolator {
+
+    private final float[] mValues;
+    private final float mStepSize;
+
+    public LookupTableInterpolator(float[] values) {
+        mValues = values;
+        mStepSize = 1f / (mValues.length - 1);
+    }
+
+    @Override
+    public float getInterpolation(float input) {
+        if (input >= 1.0f) {
+            return 1.0f;
+        }
+        if (input <= 0f) {
+            return 0f;
+        }
+
+        // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when
+        // we lerp (linearly interpolate) in the return statement
+        int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2);
+
+        // Calculate values to account for small offsets as the lookup table has discrete values
+        float quantized = position * mStepSize;
+        float diff = input - quantized;
+        float weight = diff / mStepSize;
+
+        // Linearly interpolate between the table values
+        return mValues[position] + weight * (mValues[position + 1] - mValues[position]);
+    }
+
+}
diff --git a/v4/java/android/support/v4/widget/BakedBezierInterpolator.java b/v4/java/android/support/v4/widget/BakedBezierInterpolator.java
deleted file mode 100644
index 892813c..0000000
--- a/v4/java/android/support/v4/widget/BakedBezierInterpolator.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.widget;
-
-import android.view.animation.Interpolator;
-
-/**
- * A pre-baked bezier-curved interpolator for indeterminate progress animations.
- */
-final class BakedBezierInterpolator implements Interpolator {
-    private static final BakedBezierInterpolator INSTANCE = new BakedBezierInterpolator();
-
-    public final static BakedBezierInterpolator getInstance() {
-        return INSTANCE;
-    }
-
-    /**
-     * Use getInstance instead of instantiating.
-     */
-    private BakedBezierInterpolator() {
-        super();
-    }
-
-    /**
-     * Lookup table values.
-     * Generated using a Bezier curve from (0,0) to (1,1) with control points:
-     * P0 (0,0)
-     * P1 (0.4, 0)
-     * P2 (0.2, 1.0)
-     * P3 (1.0, 1.0)
-     *
-     * Values sampled with x at regular intervals between 0 and 1.
-     */
-    private static final float[] VALUES = new float[] {
-        0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
-        0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f,
-        0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f,
-        0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f,
-        0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f,
-        0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f,
-        0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f,
-        0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f,
-        0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f,
-        0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
-    };
-
-    private static final float STEP_SIZE = 1.0f / (VALUES.length - 1);
-
-    @Override
-    public float getInterpolation(float input) {
-        if (input >= 1.0f) {
-            return 1.0f;
-        }
-
-        if (input <= 0f) {
-            return 0f;
-        }
-
-        int position = Math.min(
-                (int)(input * (VALUES.length - 1)),
-                VALUES.length - 2);
-
-        float quantized = position * STEP_SIZE;
-        float difference = input - quantized;
-        float weight = difference / STEP_SIZE;
-
-        return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]);
-    }
-
-}
diff --git a/v4/java/android/support/v4/widget/CircleImageView.java b/v4/java/android/support/v4/widget/CircleImageView.java
index 5010680..246ffa7 100644
--- a/v4/java/android/support/v4/widget/CircleImageView.java
+++ b/v4/java/android/support/v4/widget/CircleImageView.java
@@ -17,7 +17,6 @@
 package android.support.v4.widget;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -68,7 +67,7 @@
             ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
             circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
                     KEY_SHADOW_COLOR);
-            final int padding = (int) mShadowRadius;
+            final int padding = mShadowRadius;
             // set padding so the inner image sits correctly within the shadow.
             setPadding(padding, padding, padding, padding);
         }
@@ -111,17 +110,22 @@
 
     /**
      * Update the background color of the circle image view.
+     *
+     * @param colorRes Id of a color resource.
      */
-    public void setBackgroundColor(int colorRes) {
+    public void setBackgroundColorRes(int colorRes) {
+        setBackgroundColor(getContext().getResources().getColor(colorRes));
+    }
+
+    @Override
+    public void setBackgroundColor(int color) {
         if (getBackground() instanceof ShapeDrawable) {
-            final Resources res = getResources();
-            ((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes));
+            ((ShapeDrawable) getBackground()).getPaint().setColor(color);
         }
     }
 
     private class OvalShadow extends OvalShape {
         private RadialGradient mRadialGradient;
-        private int mShadowRadius;
         private Paint mShadowPaint;
         private int mCircleDiameter;
 
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 25ac1c0..a8967a2 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -160,6 +160,9 @@
             android.R.attr.layout_gravity
     };
 
+    /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */
+    private static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19;
+
     private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
             new ChildAccessibilityDelegate();
 
@@ -258,6 +261,7 @@
         void dispatchChildInsets(View child, Object insets, int drawerGravity);
         void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity);
         int getTopInset(Object lastInsets);
+        Drawable getDefaultStatusBarBackground(Context context);
     }
 
     static class DrawerLayoutCompatImplBase implements DrawerLayoutCompatImpl {
@@ -276,6 +280,11 @@
         public int getTopInset(Object insets) {
             return 0;
         }
+
+        @Override
+        public Drawable getDefaultStatusBarBackground(Context context) {
+            return null;
+        }
     }
 
     static class DrawerLayoutCompatImplApi21 implements DrawerLayoutCompatImpl {
@@ -294,6 +303,11 @@
         public int getTopInset(Object insets) {
             return DrawerLayoutCompatApi21.getTopInset(insets);
         }
+
+        @Override
+        public Drawable getDefaultStatusBarBackground(Context context) {
+            return DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context);
+        }
     }
 
     static {
@@ -345,6 +359,7 @@
         ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
         if (ViewCompat.getFitsSystemWindows(this)) {
             IMPL.configureApplyInsets(this);
+            mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
         }
     }
 
@@ -631,15 +646,7 @@
                 mListener.onDrawerClosed(drawerView);
             }
 
-            // If no drawer is opened, all drawers are not shown
-            // for accessibility and the content is shown.
-            View content = getChildAt(0);
-            if (content != null) {
-                ViewCompat.setImportantForAccessibility(content,
-                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            }
-            ViewCompat.setImportantForAccessibility(drawerView,
-                            ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            updateChildrenImportantForAccessibility(drawerView, false);
 
             // Only send WINDOW_STATE_CHANGE if the host has window focus. This
             // may change if support for multiple foreground windows (e.g. IME)
@@ -661,18 +668,31 @@
                 mListener.onDrawerOpened(drawerView);
             }
 
-            // If a drawer is opened, only it is shown for
-            // accessibility and the content is not shown.
-            View content = getChildAt(0);
-            if (content != null) {
-                ViewCompat.setImportantForAccessibility(content,
+            updateChildrenImportantForAccessibility(drawerView, true);
+
+            // Only send WINDOW_STATE_CHANGE if the host has window focus.
+            if (hasWindowFocus()) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            }
+
+            drawerView.requestFocus();
+        }
+    }
+
+    private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (!isDrawerOpen && !isDrawerView(child)
+                    || isDrawerOpen && child == drawerView) {
+                // Drawer is closed and this is a content view or this is an
+                // open drawer view, so it should be visible.
+                ViewCompat.setImportantForAccessibility(child,
+                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            } else {
+                ViewCompat.setImportantForAccessibility(child,
                         ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
             }
-            ViewCompat.setImportantForAccessibility(drawerView,
-                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-            drawerView.requestFocus();
         }
     }
 
@@ -992,6 +1012,16 @@
      */
     public void setStatusBarBackground(Drawable bg) {
         mStatusBarBackground = bg;
+        invalidate();
+    }
+
+    /**
+     * Gets the drawable used to draw in the insets area for the status bar.
+     *
+     * @return The status bar background drawable, or null if none set
+     */
+    public Drawable getStatusBarBackgroundDrawable() {
+        return mStatusBarBackground;
     }
 
     /**
@@ -1002,6 +1032,7 @@
      */
     public void setStatusBarBackground(int resId) {
         mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null;
+        invalidate();
     }
 
     /**
@@ -1013,6 +1044,7 @@
      */
     public void setStatusBarBackgroundColor(int color) {
         mStatusBarBackground = new ColorDrawable(color);
+        invalidate();
     }
 
     @Override
@@ -1116,9 +1148,11 @@
                 final float y = ev.getY();
                 mInitialMotionX = x;
                 mInitialMotionY = y;
-                if (mScrimOpacity > 0 &&
-                        isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
-                    interceptForTap = true;
+                if (mScrimOpacity > 0) {
+                    final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
+                    if (child != null && isContentView(child)) {
+                        interceptForTap = true;
+                    }
                 }
                 mDisallowInterceptRequested = false;
                 mChildrenCanceledTouch = false;
@@ -1264,13 +1298,7 @@
             lp.onScreen = 1.f;
             lp.knownOpen = true;
 
-            View content = getChildAt(0);
-            if (content != null) {
-                ViewCompat.setImportantForAccessibility(content,
-                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            }
-            ViewCompat.setImportantForAccessibility(drawerView,
-                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            updateChildrenImportantForAccessibility(drawerView, true);
         } else {
             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
                 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
@@ -1507,22 +1535,11 @@
     @Override
     protected Parcelable onSaveInstanceState() {
         final Parcelable superState = super.onSaveInstanceState();
-
         final SavedState ss = new SavedState(superState);
 
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (!isDrawerView(child)) {
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (lp.knownOpen) {
-                ss.openDrawerGravity = lp.gravity;
-                // Only one drawer can be open at a time.
-                break;
-            }
+        final View openDrawer = findOpenDrawer();
+        if (openDrawer != null) {
+            ss.openDrawerGravity = ((LayoutParams) openDrawer.getLayoutParams()).gravity;
         }
 
         ss.lockModeLeft = mLockModeLeft;
@@ -1533,19 +1550,26 @@
 
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
-        // Until a drawer is open, it is hidden from accessibility.
-        if (index > 0 || (index < 0 && getChildCount() > 0)) {
+        super.addView(child, index, params);
+
+        final View openDrawer = findOpenDrawer();
+        if (openDrawer != null || isDrawerView(child)) {
+            // A drawer is already open or the new view is a drawer, so the
+            // new view should start out hidden.
             ViewCompat.setImportantForAccessibility(child,
                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            // Also set a delegate to break the child-parent relation if the
-            // child is hidden. For details (see incluceChildForAccessibility).
-            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
-        } else  {
-            // Initially, the content is shown for accessibility.
+        } else {
+            // Otherwise this is a content view and no drawer is open, so the
+            // new view should start out visible.
             ViewCompat.setImportantForAccessibility(child,
                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
-        super.addView(child, index, params);
+
+        // We only need a delegate here if the framework doesn't understand
+        // NO_HIDE_DESCENDANTS importance.
+        if (!CAN_HIDE_DESCENDANTS) {
+            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
+        }
     }
 
     private static boolean includeChildForAccessibility(View child) {
@@ -1806,20 +1830,33 @@
 
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
-            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
-            super.onInitializeAccessibilityNodeInfo(host, superNode);
+            if (CAN_HIDE_DESCENDANTS) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+            } else {
+                // Obtain a node for the host, then manually generate the list
+                // of children to only include non-obscured views.
+                final AccessibilityNodeInfoCompat superNode =
+                        AccessibilityNodeInfoCompat.obtain(info);
+                super.onInitializeAccessibilityNodeInfo(host, superNode);
+
+                info.setSource(host);
+                final ViewParent parent = ViewCompat.getParentForAccessibility(host);
+                if (parent instanceof View) {
+                    info.setParent((View) parent);
+                }
+                copyNodeInfoNoChildren(info, superNode);
+                superNode.recycle();
+
+                addChildrenForAccessibility(info, (ViewGroup) host);
+            }
 
             info.setClassName(DrawerLayout.class.getName());
-            info.setSource(host);
-            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
-            if (parent instanceof View) {
-                info.setParent((View) parent);
-            }
-            copyNodeInfoNoChildren(info, superNode);
 
-            superNode.recycle();
-
-            addChildrenForAccessibility(info, (ViewGroup) host);
+            // This view reports itself as focusable so that it can intercept
+            // the back button, but we should prevent this view from reporting
+            // itself as focusable to accessibility services.
+            info.setFocusable(false);
+            info.setFocused(false);
         }
 
         @Override
@@ -1853,6 +1890,15 @@
             return super.dispatchPopulateAccessibilityEvent(host, event);
         }
 
+        @Override
+        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
+                AccessibilityEvent event) {
+            if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
+                return super.onRequestSendAccessibilityEvent(host, child, event);
+            }
+            return false;
+        }
+
         private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
             final int childCount = v.getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -1863,15 +1909,6 @@
             }
         }
 
-        @Override
-        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
-                AccessibilityEvent event) {
-            if (includeChildForAccessibility(child)) {
-                return super.onRequestSendAccessibilityEvent(host, child, event);
-            }
-            return false;
-        }
-
         /**
          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
          * seem to be a few elements that are not easily cloneable using the underlying API.
@@ -1909,6 +1946,7 @@
         public void onInitializeAccessibilityNodeInfo(View child,
                 AccessibilityNodeInfoCompat info) {
             super.onInitializeAccessibilityNodeInfo(child, info);
+
             if (!includeChildForAccessibility(child)) {
                 // If we are ignoring the sub-tree rooted at the child,
                 // break the connection to the rest of the node tree.
diff --git a/v4/java/android/support/v4/widget/EdgeEffectCompat.java b/v4/java/android/support/v4/widget/EdgeEffectCompat.java
index 79b413a..51da4cd 100644
--- a/v4/java/android/support/v4/widget/EdgeEffectCompat.java
+++ b/v4/java/android/support/v4/widget/EdgeEffectCompat.java
@@ -50,6 +50,7 @@
         public boolean onRelease(Object edgeEffect);
         public boolean onAbsorb(Object edgeEffect, int velocity);
         public boolean draw(Object edgeEffect, Canvas canvas);
+        public boolean onPull(Object edgeEffect, float deltaDistance, float displacement);
     }
 
     /**
@@ -85,6 +86,10 @@
         public boolean draw(Object edgeEffect, Canvas canvas) {
             return false;
         }
+
+        public boolean onPull(Object edgeEffect, float deltaDistance, float displacement) {
+            return false;
+        }
     }
 
     static class EdgeEffectIcsImpl implements EdgeEffectImpl {
@@ -119,6 +124,16 @@
         public boolean draw(Object edgeEffect, Canvas canvas) {
             return EdgeEffectCompatIcs.draw(edgeEffect, canvas);
         }
+
+        public boolean onPull(Object edgeEffect, float deltaDistance, float displacement) {
+            return EdgeEffectCompatIcs.onPull(edgeEffect, deltaDistance);
+        }
+    }
+
+    static class EdgeEffectLollipopImpl extends EdgeEffectIcsImpl {
+        public boolean onPull(Object edgeEffect, float deltaDistance, float displacement) {
+            return EdgeEffectCompatLollipop.onPull(edgeEffect, deltaDistance, displacement);
+        }
     }
 
     /**
@@ -172,12 +187,31 @@
      *                      1.f (full length of the view) or negative values to express change
      *                      back toward the edge reached to initiate the effect.
      * @return true if the host view should call invalidate, false if it should not.
+     * @deprecated use {@link #onPull(float, float)}
      */
     public boolean onPull(float deltaDistance) {
         return IMPL.onPull(mEdgeEffect, deltaDistance);
     }
 
     /**
+     * A view should call this when content is pulled away from an edge by the user.
+     * This will update the state of the current visual effect and its associated animation.
+     * The host view should always {@link android.view.View#invalidate()} if this method
+     * returns true and draw the results accordingly.
+     *
+     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
+     *                      1.f (full length of the view) or negative values to express change
+     *                      back toward the edge reached to initiate the effect.
+     * @param displacement The displacement from the starting side of the effect of the point
+     *                     initiating the pull. In the case of touch this is the finger position.
+     *                     Values may be from 0-1.
+     * @return true if the host view should call invalidate, false if it should not.
+     */
+    public boolean onPull(float deltaDistance, float displacement) {
+        return IMPL.onPull(mEdgeEffect, deltaDistance, displacement);
+    }
+
+    /**
      * Call when the object is released after being pulled.
      * This will begin the "decay" phase of the effect. After calling this method
      * the host view should {@link android.view.View#invalidate()} if this method
diff --git a/v4/java/android/support/v4/widget/MaterialProgressDrawable.java b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
index 153c493..fc7eb26 100644
--- a/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
+++ b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
@@ -26,7 +26,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
-import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
 import android.graphics.Path;
@@ -107,7 +106,7 @@
     private float mRotationCount;
     private double mWidth;
     private double mHeight;
-    private Animation mFinishAnimation;
+    boolean mFinishing;
 
     public MaterialProgressDrawable(Context context, View parent) {
         mParent = parent;
@@ -273,10 +272,13 @@
         mRing.storeOriginals();
         // Already showing some part of the ring
         if (mRing.getEndTrim() != mRing.getStartTrim()) {
-            mParent.startAnimation(mFinishAnimation);
+            mFinishing = true;
+            mAnimation.setDuration(ANIMATION_DURATION/2);
+            mParent.startAnimation(mAnimation);
         } else {
             mRing.setColorIndex(0);
             mRing.resetOriginals();
+            mAnimation.setDuration(ANIMATION_DURATION);
             mParent.startAnimation(mAnimation);
         }
     }
@@ -290,99 +292,88 @@
         mRing.resetOriginals();
     }
 
+    private void applyFinishTranslation(float interpolatedTime, Ring ring) {
+        // shrink back down and complete a full rotation before
+        // starting other circles
+        // Rotation goes between [0..1].
+        float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+                + 1f);
+        final float startTrim = ring.getStartingStartTrim()
+                + (ring.getStartingEndTrim() - ring.getStartingStartTrim()) * interpolatedTime;
+        ring.setStartTrim(startTrim);
+        final float rotation = ring.getStartingRotation()
+                + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
+        ring.setRotation(rotation);
+    }
+
     private void setupAnimators() {
         final Ring ring = mRing;
-        final Animation finishRingAnimation = new Animation() {
-            public void applyTransformation(float interpolatedTime, Transformation t) {
-                // shrink back down and complete a full rotation before starting other circles
-                // Rotation goes between [0..1].
-                float targetRotation = (float) (Math.floor(ring.getStartingRotation()
-                        / MAX_PROGRESS_ARC) + 1f);
-                final float startTrim = ring.getStartingStartTrim()
-                        + (ring.getStartingEndTrim() - ring.getStartingStartTrim())
-                        * interpolatedTime;
-                ring.setStartTrim(startTrim);
-                final float rotation = ring.getStartingRotation()
-                        + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
-                ring.setRotation(rotation);
-                ring.setArrowScale(1 - interpolatedTime);
-            }
-        };
-        finishRingAnimation.setInterpolator(EASE_INTERPOLATOR);
-        finishRingAnimation.setDuration(ANIMATION_DURATION/2);
-        finishRingAnimation.setAnimationListener(new Animation.AnimationListener() {
-
-            @Override
-            public void onAnimationStart(Animation animation) {
-            }
-
-            @Override
-            public void onAnimationEnd(Animation animation) {
-                ring.goToNextColor();
-                ring.storeOriginals();
-                ring.setShowArrow(false);
-                mParent.startAnimation(mAnimation);
-            }
-
-            @Override
-            public void onAnimationRepeat(Animation animation) {
-            }
-        });
         final Animation animation = new Animation() {
-            @Override
+                @Override
             public void applyTransformation(float interpolatedTime, Transformation t) {
-                // The minProgressArc is calculated from 0 to create an angle that
-                // matches the stroke width.
-                final float minProgressArc = (float) Math.toRadians(ring.getStrokeWidth()
-                        / (2 * Math.PI * ring.getCenterRadius()));
-                final float startingEndTrim = ring.getStartingEndTrim();
-                final float startingTrim = ring.getStartingStartTrim();
-                final float startingRotation = ring.getStartingRotation();
+                if (mFinishing) {
+                    applyFinishTranslation(interpolatedTime, ring);
+                } else {
+                    // The minProgressArc is calculated from 0 to create an
+                    // angle that
+                    // matches the stroke width.
+                    final float minProgressArc = (float) Math.toRadians(
+                            ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
+                    final float startingEndTrim = ring.getStartingEndTrim();
+                    final float startingTrim = ring.getStartingStartTrim();
+                    final float startingRotation = ring.getStartingRotation();
 
-                // Offset the minProgressArc to where the endTrim is located.
-                final float minArc = MAX_PROGRESS_ARC - minProgressArc;
-                final float endTrim = startingEndTrim
-                        + (minArc * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
-                ring.setEndTrim(endTrim);
+                    // Offset the minProgressArc to where the endTrim is
+                    // located.
+                    final float minArc = MAX_PROGRESS_ARC - minProgressArc;
+                    final float endTrim = startingEndTrim + (minArc
+                            * START_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+                    ring.setEndTrim(endTrim);
 
-                final float startTrim = startingTrim
-                        + (MAX_PROGRESS_ARC * END_CURVE_INTERPOLATOR
-                                .getInterpolation(interpolatedTime));
-                ring.setStartTrim(startTrim);
+                    final float startTrim = startingTrim + (MAX_PROGRESS_ARC
+                            * END_CURVE_INTERPOLATOR.getInterpolation(interpolatedTime));
+                    ring.setStartTrim(startTrim);
 
-                final float rotation = startingRotation + (0.25f * interpolatedTime);
-                ring.setRotation(rotation);
+                    final float rotation = startingRotation + (0.25f * interpolatedTime);
+                    ring.setRotation(rotation);
 
-                float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
-                        + (720.0f * (mRotationCount / NUM_POINTS));
-                setRotation(groupRotation);
+                    float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
+                            + (720.0f * (mRotationCount / NUM_POINTS));
+                    setRotation(groupRotation);
+                }
             }
         };
         animation.setRepeatCount(Animation.INFINITE);
         animation.setRepeatMode(Animation.RESTART);
         animation.setInterpolator(LINEAR_INTERPOLATOR);
-        animation.setDuration(ANIMATION_DURATION);
         animation.setAnimationListener(new Animation.AnimationListener() {
 
-            @Override
+                @Override
             public void onAnimationStart(Animation animation) {
                 mRotationCount = 0;
             }
 
-            @Override
+                @Override
             public void onAnimationEnd(Animation animation) {
                 // do nothing
             }
 
-            @Override
+                @Override
             public void onAnimationRepeat(Animation animation) {
                 ring.storeOriginals();
                 ring.goToNextColor();
                 ring.setStartTrim(ring.getEndTrim());
-                mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+                if (mFinishing) {
+                    // finished closing the last ring from the swipe gesture; go
+                    // into progress mode
+                    mFinishing = false;
+                    animation.setDuration(ANIMATION_DURATION);
+                    ring.setShowArrow(false);
+                } else {
+                    mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+                }
             }
         });
-        mFinishAnimation = finishRingAnimation;
         mAnimation = animation;
     }
 
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
new file mode 100644
index 0000000..eb24814
--- /dev/null
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -0,0 +1,1862 @@
+/*
+ * 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.v4.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.InputDeviceCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.NestedScrollingChild;
+import android.support.v4.view.NestedScrollingChildHelper;
+import android.support.v4.view.NestedScrollingParent;
+import android.support.v4.view.NestedScrollingParentHelper;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.ScrollView;
+
+import java.util.List;
+
+/**
+ * NestedScrollView is just like {@link android.widget.ScrollView}, but it supports acting
+ * as both a nested scrolling parent and child on both new and old versions of Android.
+ * Nested scrolling is enabled by default.
+ */
+public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
+        NestedScrollingChild {
+    static final int ANIMATED_SCROLL_GAP = 250;
+
+    static final float MAX_SCROLL_FACTOR = 0.5f;
+
+    private static final String TAG = "NestedScrollView";
+
+    private long mLastScroll;
+
+    private final Rect mTempRect = new Rect();
+    private ScrollerCompat mScroller;
+    private EdgeEffectCompat mEdgeGlowTop;
+    private EdgeEffectCompat mEdgeGlowBottom;
+
+    /**
+     * Position of the last motion event.
+     */
+    private int mLastMotionY;
+
+    /**
+     * True when the layout has changed but the traversal has not come through yet.
+     * Ideally the view hierarchy would keep track of this for us.
+     */
+    private boolean mIsLayoutDirty = true;
+    private boolean mIsLaidOut = false;
+
+    /**
+     * The child to give focus to in the event that a child has requested focus while the
+     * layout is dirty. This prevents the scroll from being wrong if the child has not been
+     * laid out before requesting focus.
+     */
+    private View mChildToScrollTo = null;
+
+    /**
+     * True if the user is currently dragging this ScrollView around. This is
+     * not the same as 'is being flinged', which can be checked by
+     * mScroller.isFinished() (flinging begins when the user lifts his finger).
+     */
+    private boolean mIsBeingDragged = false;
+
+    /**
+     * Determines speed during touch scrolling
+     */
+    private VelocityTracker mVelocityTracker;
+
+    /**
+     * When set to true, the scroll view measure its child to make it fill the currently
+     * visible area.
+     */
+    private boolean mFillViewport;
+
+    /**
+     * Whether arrow scrolling is animated.
+     */
+    private boolean mSmoothScrollingEnabled = true;
+
+    private int mTouchSlop;
+    private int mMinimumVelocity;
+    private int mMaximumVelocity;
+
+    /**
+     * ID of the active pointer. This is used to retain consistency during
+     * drags/flings if multiple pointers are used.
+     */
+    private int mActivePointerId = INVALID_POINTER;
+
+    /**
+     * Used during scrolling to retrieve the new offset within the window.
+     */
+    private final int[] mScrollOffset = new int[2];
+    private final int[] mScrollConsumed = new int[2];
+    private int mNestedYOffset;
+
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+
+    private SavedState mSavedState;
+
+    private static final AccessibilityDelegate ACCESSIBILITY_DELEGATE = new AccessibilityDelegate();
+
+    private static final int[] SCROLLVIEW_STYLEABLE = new int[] {
+            android.R.attr.fillViewport
+    };
+
+    private final NestedScrollingParentHelper mParentHelper;
+    private final NestedScrollingChildHelper mChildHelper;
+
+    private float mVerticalScrollFactor;
+
+    public NestedScrollView(Context context) {
+        this(context, null);
+    }
+
+    public NestedScrollView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initScrollView();
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0);
+
+        setFillViewport(a.getBoolean(0, false));
+
+        a.recycle();
+
+        mParentHelper = new NestedScrollingParentHelper(this);
+        mChildHelper = new NestedScrollingChildHelper(this);
+
+        // ...because why else would you be using this widget?
+        setNestedScrollingEnabled(true);
+
+        ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
+    }
+
+    // NestedScrollingChild
+
+    @Override
+    public void setNestedScrollingEnabled(boolean enabled) {
+        mChildHelper.setNestedScrollingEnabled(enabled);
+    }
+
+    @Override
+    public boolean isNestedScrollingEnabled() {
+        return mChildHelper.isNestedScrollingEnabled();
+    }
+
+    @Override
+    public boolean startNestedScroll(int axes) {
+        return mChildHelper.startNestedScroll(axes);
+    }
+
+    @Override
+    public void stopNestedScroll() {
+        mChildHelper.stopNestedScroll();
+    }
+
+    @Override
+    public boolean hasNestedScrollingParent() {
+        return mChildHelper.hasNestedScrollingParent();
+    }
+
+    @Override
+    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
+            int dyUnconsumed, int[] offsetInWindow) {
+        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                offsetInWindow);
+    }
+
+    @Override
+    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
+    }
+
+    @Override
+    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
+        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
+    }
+
+    @Override
+    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
+    }
+
+    // NestedScrollingParent
+
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+        return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
+    }
+
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+        mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
+        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+    }
+
+    @Override
+    public void onStopNestedScroll(View target) {
+        stopNestedScroll();
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
+            int dyUnconsumed) {
+        final int oldScrollY = getScrollY();
+        scrollBy(0, dyUnconsumed);
+        final int myConsumed = getScrollY() - oldScrollY;
+        final int myUnconsumed = dyUnconsumed - myConsumed;
+        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
+    }
+
+    @Override
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+        // Do nothing
+    }
+
+    @Override
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+        if (!consumed) {
+            flingWithNestedDispatch((int) velocityY);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        // Do nothing
+        return false;
+    }
+
+    @Override
+    public int getNestedScrollAxes() {
+        return mParentHelper.getNestedScrollAxes();
+    }
+
+    // ScrollView import
+
+    public boolean shouldDelayChildPressedState() {
+        return true;
+    }
+
+    @Override
+    protected float getTopFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getVerticalFadingEdgeLength();
+        final int scrollY = getScrollY();
+        if (scrollY < length) {
+            return scrollY / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    @Override
+    protected float getBottomFadingEdgeStrength() {
+        if (getChildCount() == 0) {
+            return 0.0f;
+        }
+
+        final int length = getVerticalFadingEdgeLength();
+        final int bottomEdge = getHeight() - getPaddingBottom();
+        final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge;
+        if (span < length) {
+            return span / (float) length;
+        }
+
+        return 1.0f;
+    }
+
+    /**
+     * @return The maximum amount this scroll view will scroll in response to
+     *   an arrow event.
+     */
+    public int getMaxScrollAmount() {
+        return (int) (MAX_SCROLL_FACTOR * getHeight());
+    }
+
+    private void initScrollView() {
+        mScroller = new ScrollerCompat(getContext(), null);
+        setFocusable(true);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setWillNotDraw(false);
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+    }
+
+    @Override
+    public void addView(View child) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child);
+    }
+
+    @Override
+    public void addView(View child, int index) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, index);
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, params);
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (getChildCount() > 0) {
+            throw new IllegalStateException("ScrollView can host only one direct child");
+        }
+
+        super.addView(child, index, params);
+    }
+
+    /**
+     * @return Returns true this ScrollView can be scrolled
+     */
+    private boolean canScroll() {
+        View child = getChildAt(0);
+        if (child != null) {
+            int childHeight = child.getHeight();
+            return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether this ScrollView's content is stretched to fill the viewport.
+     *
+     * @return True if the content fills the viewport, false otherwise.
+     *
+     * @attr ref android.R.styleable#ScrollView_fillViewport
+     */
+    public boolean isFillViewport() {
+        return mFillViewport;
+    }
+
+    /**
+     * Indicates this ScrollView whether it should stretch its content height to fill
+     * the viewport or not.
+     *
+     * @param fillViewport True to stretch the content's height to the viewport's
+     *        boundaries, false otherwise.
+     *
+     * @attr ref android.R.styleable#ScrollView_fillViewport
+     */
+    public void setFillViewport(boolean fillViewport) {
+        if (fillViewport != mFillViewport) {
+            mFillViewport = fillViewport;
+            requestLayout();
+        }
+    }
+
+    /**
+     * @return Whether arrow scrolling will animate its transition.
+     */
+    public boolean isSmoothScrollingEnabled() {
+        return mSmoothScrollingEnabled;
+    }
+
+    /**
+     * Set whether arrow scrolling will animate its transition.
+     * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
+     */
+    public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
+        mSmoothScrollingEnabled = smoothScrollingEnabled;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!mFillViewport) {
+            return;
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
+            return;
+        }
+
+        if (getChildCount() > 0) {
+            final View child = getChildAt(0);
+            int height = getMeasuredHeight();
+            if (child.getMeasuredHeight() < height) {
+                final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+                        getPaddingLeft() + getPaddingRight(), lp.width);
+                height -= getPaddingTop();
+                height -= getPaddingBottom();
+                int childHeightMeasureSpec =
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+            }
+        }
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        // Let the focused view and/or our descendants get the key first
+        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
+    }
+
+    /**
+     * You can call this function yourself to have the scroll view perform
+     * scrolling from a key event, just as if the event had been dispatched to
+     * it by the view hierarchy.
+     *
+     * @param event The key event to execute.
+     * @return Return true if the event was handled, else false.
+     */
+    public boolean executeKeyEvent(KeyEvent event) {
+        mTempRect.setEmpty();
+
+        if (!canScroll()) {
+            if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                View currentFocused = findFocus();
+                if (currentFocused == this) currentFocused = null;
+                View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+                        currentFocused, View.FOCUS_DOWN);
+                return nextFocused != null
+                        && nextFocused != this
+                        && nextFocused.requestFocus(View.FOCUS_DOWN);
+            }
+            return false;
+        }
+
+        boolean handled = false;
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_UP);
+                    } else {
+                        handled = fullScroll(View.FOCUS_UP);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (!event.isAltPressed()) {
+                        handled = arrowScroll(View.FOCUS_DOWN);
+                    } else {
+                        handled = fullScroll(View.FOCUS_DOWN);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_SPACE:
+                    pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
+                    break;
+            }
+        }
+
+        return handled;
+    }
+
+    private boolean inChild(int x, int y) {
+        if (getChildCount() > 0) {
+            final int scrollY = getScrollY();
+            final View child = getChildAt(0);
+            return !(y < child.getTop() - scrollY
+                    || y >= child.getBottom() - scrollY
+                    || x < child.getLeft()
+                    || x >= child.getRight());
+        }
+        return false;
+    }
+
+    private void initOrResetVelocityTracker() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        } else {
+            mVelocityTracker.clear();
+        }
+    }
+
+    private void initVelocityTrackerIfNotExists() {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+    }
+
+    private void recycleVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        if (disallowIntercept) {
+            recycleVelocityTracker();
+        }
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+    }
+
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        /*
+         * This method JUST determines whether we want to intercept the motion.
+         * If we return true, onMotionEvent will be called and we do the actual
+         * scrolling there.
+         */
+
+        /*
+        * Shortcut the most recurring case: the user is in the dragging
+        * state and he is moving his finger.  We want to intercept this
+        * motion.
+        */
+        final int action = ev.getAction();
+        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
+            return true;
+        }
+
+        /*
+         * Don't try to intercept touch if we can't scroll anyway.
+         */
+        if (getScrollY() == 0 && !ViewCompat.canScrollVertically(this, 1)) {
+            return false;
+        }
+
+        switch (action & MotionEventCompat.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
+                /*
+                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+                 * whether the user has moved far enough from his original down touch.
+                 */
+
+                /*
+                * Locally do absolute value. mLastMotionY is set to the y value
+                * of the down event.
+                */
+                final int activePointerId = mActivePointerId;
+                if (activePointerId == INVALID_POINTER) {
+                    // If we don't have a valid id, the touch down wasn't on content.
+                    break;
+                }
+
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
+                if (pointerIndex == -1) {
+                    Log.e(TAG, "Invalid pointerId=" + activePointerId
+                            + " in onInterceptTouchEvent");
+                    break;
+                }
+
+                final int y = (int) MotionEventCompat.getY(ev, pointerIndex);
+                final int yDiff = Math.abs(y - mLastMotionY);
+                if (yDiff > mTouchSlop
+                        && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
+                    mIsBeingDragged = true;
+                    mLastMotionY = y;
+                    initVelocityTrackerIfNotExists();
+                    mVelocityTracker.addMovement(ev);
+                    mNestedYOffset = 0;
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_DOWN: {
+                final int y = (int) ev.getY();
+                if (!inChild((int) ev.getX(), (int) y)) {
+                    mIsBeingDragged = false;
+                    recycleVelocityTracker();
+                    break;
+                }
+
+                /*
+                 * Remember location of down touch.
+                 * ACTION_DOWN always refers to pointer index 0.
+                 */
+                mLastMotionY = y;
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+
+                initOrResetVelocityTracker();
+                mVelocityTracker.addMovement(ev);
+                /*
+                * If being flinged and user touches the screen, initiate drag;
+                * otherwise don't.  mScroller.isFinished should be false when
+                * being flinged.
+                */
+                mIsBeingDragged = !mScroller.isFinished();
+                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+                break;
+            }
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                /* Release the drag */
+                mIsBeingDragged = false;
+                mActivePointerId = INVALID_POINTER;
+                recycleVelocityTracker();
+                stopNestedScroll();
+                break;
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+        }
+
+        /*
+        * The only time we want to intercept motion events is if we are in the
+        * drag mode.
+        */
+        return mIsBeingDragged;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        initVelocityTrackerIfNotExists();
+
+        MotionEvent vtev = MotionEvent.obtain(ev);
+
+        final int actionMasked = MotionEventCompat.getActionMasked(ev);
+
+        if (actionMasked == MotionEvent.ACTION_DOWN) {
+            mNestedYOffset = 0;
+        }
+        vtev.offsetLocation(0, mNestedYOffset);
+
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN: {
+                if (getChildCount() == 0) {
+                    return false;
+                }
+                if ((mIsBeingDragged = !mScroller.isFinished())) {
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                }
+
+                /*
+                 * If being flinged and user touches, stop the fling. isFinished
+                 * will be false if being flinged.
+                 */
+                if (!mScroller.isFinished()) {
+                    mScroller.abortAnimation();
+                }
+
+                // Remember where the motion event started
+                mLastMotionY = (int) ev.getY();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE:
+                final int activePointerIndex = MotionEventCompat.findPointerIndex(ev,
+                        mActivePointerId);
+                if (activePointerIndex == -1) {
+                    Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
+                    break;
+                }
+
+                final int y = (int) MotionEventCompat.getY(ev, activePointerIndex);
+                int deltaY = mLastMotionY - y;
+                if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
+                    deltaY -= mScrollConsumed[1];
+                    vtev.offsetLocation(0, mScrollOffset[1]);
+                    mNestedYOffset += mScrollOffset[1];
+                }
+                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                    mIsBeingDragged = true;
+                    if (deltaY > 0) {
+                        deltaY -= mTouchSlop;
+                    } else {
+                        deltaY += mTouchSlop;
+                    }
+                }
+                if (mIsBeingDragged) {
+                    // Scroll to follow the motion event
+                    mLastMotionY = y - mScrollOffset[1];
+
+                    final int oldY = getScrollY();
+                    final int range = getScrollRange();
+                    final int overscrollMode = ViewCompat.getOverScrollMode(this);
+                    boolean canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+                            (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                                    range > 0);
+
+                    // Calling overScrollByCompat will call onOverScrolled, which
+                    // calls onScrollChanged if applicable.
+                    if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0,
+                            0, true) && !hasNestedScrollingParent()) {
+                        // Break our velocity if we hit a scroll barrier.
+                        mVelocityTracker.clear();
+                    }
+
+                    final int scrolledDeltaY = getScrollY() - oldY;
+                    final int unconsumedY = deltaY - scrolledDeltaY;
+                    if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
+                        mLastMotionY -= mScrollOffset[1];
+                        vtev.offsetLocation(0, mScrollOffset[1]);
+                        mNestedYOffset += mScrollOffset[1];
+                    } else if (canOverscroll) {
+                        ensureGlows();
+                        final int pulledToY = oldY + deltaY;
+                        if (pulledToY < 0) {
+                            mEdgeGlowTop.onPull((float) deltaY / getHeight(),
+                                    MotionEventCompat.getX(ev, activePointerIndex) / getWidth());
+                            if (!mEdgeGlowBottom.isFinished()) {
+                                mEdgeGlowBottom.onRelease();
+                            }
+                        } else if (pulledToY > range) {
+                            mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
+                                    1.f - MotionEventCompat.getX(ev, activePointerIndex)
+                                            / getWidth());
+                            if (!mEdgeGlowTop.isFinished()) {
+                                mEdgeGlowTop.onRelease();
+                            }
+                        }
+                        if (mEdgeGlowTop != null
+                                && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
+                            ViewCompat.postInvalidateOnAnimation(this);
+                        }
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (mIsBeingDragged) {
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                    int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
+                            mActivePointerId);
+
+                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+                        flingWithNestedDispatch(-initialVelocity);
+                    }
+
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                if (mIsBeingDragged && getChildCount() > 0) {
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
+                }
+                break;
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                final int index = MotionEventCompat.getActionIndex(ev);
+                mLastMotionY = (int) MotionEventCompat.getY(ev, index);
+                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+                break;
+            }
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                mLastMotionY = (int) MotionEventCompat.getY(ev,
+                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
+                break;
+        }
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.addMovement(vtev);
+        }
+        vtev.recycle();
+        return true;
+    }
+
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = (ev.getAction() & MotionEventCompat.ACTION_POINTER_INDEX_MASK) >>
+                MotionEventCompat.ACTION_POINTER_INDEX_SHIFT;
+        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            // TODO: Make this decision more intelligent.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionY = (int) MotionEventCompat.getY(ev, newPointerIndex);
+            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
+
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        if ((MotionEventCompat.getSource(event) & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) {
+            switch (event.getAction()) {
+                case MotionEventCompat.ACTION_SCROLL: {
+                    if (!mIsBeingDragged) {
+                        final float vscroll = MotionEventCompat.getAxisValue(event,
+                                MotionEventCompat.AXIS_VSCROLL);
+                        if (vscroll != 0) {
+                            final int delta = (int) (vscroll * getVerticalScrollFactorCompat());
+                            final int range = getScrollRange();
+                            int oldScrollY = getScrollY();
+                            int newScrollY = oldScrollY - delta;
+                            if (newScrollY < 0) {
+                                newScrollY = 0;
+                            } else if (newScrollY > range) {
+                                newScrollY = range;
+                            }
+                            if (newScrollY != oldScrollY) {
+                                super.scrollTo(getScrollX(), newScrollY);
+                                return true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private float getVerticalScrollFactorCompat() {
+        if (mVerticalScrollFactor == 0) {
+            TypedValue outValue = new TypedValue();
+            final Context context = getContext();
+            if (!context.getTheme().resolveAttribute(
+                    android.R.attr.listPreferredItemHeight, outValue, true)) {
+                throw new IllegalStateException(
+                        "Expected theme to define listPreferredItemHeight.");
+            }
+            mVerticalScrollFactor = outValue.getDimension(
+                    context.getResources().getDisplayMetrics());
+        }
+        return mVerticalScrollFactor;
+    }
+
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        super.scrollTo(scrollX, scrollY);
+    }
+
+    boolean overScrollByCompat(int deltaX, int deltaY,
+            int scrollX, int scrollY,
+            int scrollRangeX, int scrollRangeY,
+            int maxOverScrollX, int maxOverScrollY,
+            boolean isTouchEvent) {
+        final int overScrollMode = ViewCompat.getOverScrollMode(this);
+        final boolean canScrollHorizontal =
+                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+        final boolean canScrollVertical =
+                computeVerticalScrollRange() > computeVerticalScrollExtent();
+        final boolean overScrollHorizontal = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+        final boolean overScrollVertical = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+                (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+        int newScrollX = scrollX + deltaX;
+        if (!overScrollHorizontal) {
+            maxOverScrollX = 0;
+        }
+
+        int newScrollY = scrollY + deltaY;
+        if (!overScrollVertical) {
+            maxOverScrollY = 0;
+        }
+
+        // Clamp values if at the limits and record
+        final int left = -maxOverScrollX;
+        final int right = maxOverScrollX + scrollRangeX;
+        final int top = -maxOverScrollY;
+        final int bottom = maxOverScrollY + scrollRangeY;
+
+        boolean clampedX = false;
+        if (newScrollX > right) {
+            newScrollX = right;
+            clampedX = true;
+        } else if (newScrollX < left) {
+            newScrollX = left;
+            clampedX = true;
+        }
+
+        boolean clampedY = false;
+        if (newScrollY > bottom) {
+            newScrollY = bottom;
+            clampedY = true;
+        } else if (newScrollY < top) {
+            newScrollY = top;
+            clampedY = true;
+        }
+
+        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+        return clampedX || clampedY;
+    }
+
+    private int getScrollRange() {
+        int scrollRange = 0;
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            scrollRange = Math.max(0,
+                    child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
+        }
+        return scrollRange;
+    }
+
+    /**
+     * <p>
+     * Finds the next focusable component that fits in the specified bounds.
+     * </p>
+     *
+     * @param topFocus look for a candidate is the one at the top of the bounds
+     *                 if topFocus is true, or at the bottom of the bounds if topFocus is
+     *                 false
+     * @param top      the top offset of the bounds in which a focusable must be
+     *                 found
+     * @param bottom   the bottom offset of the bounds in which a focusable must
+     *                 be found
+     * @return the next focusable component in the bounds or null if none can
+     *         be found
+     */
+    private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
+
+        List<View> focusables = getFocusables(View.FOCUS_FORWARD);
+        View focusCandidate = null;
+
+        /*
+         * A fully contained focusable is one where its top is below the bound's
+         * top, and its bottom is above the bound's bottom. A partially
+         * contained focusable is one where some part of it is within the
+         * bounds, but it also has some part that is not within bounds.  A fully contained
+         * focusable is preferred to a partially contained focusable.
+         */
+        boolean foundFullyContainedFocusable = false;
+
+        int count = focusables.size();
+        for (int i = 0; i < count; i++) {
+            View view = focusables.get(i);
+            int viewTop = view.getTop();
+            int viewBottom = view.getBottom();
+
+            if (top < viewBottom && viewTop < bottom) {
+                /*
+                 * the focusable is in the target area, it is a candidate for
+                 * focusing
+                 */
+
+                final boolean viewIsFullyContained = (top < viewTop) &&
+                        (viewBottom < bottom);
+
+                if (focusCandidate == null) {
+                    /* No candidate, take this one */
+                    focusCandidate = view;
+                    foundFullyContainedFocusable = viewIsFullyContained;
+                } else {
+                    final boolean viewIsCloserToBoundary =
+                            (topFocus && viewTop < focusCandidate.getTop()) ||
+                                    (!topFocus && viewBottom > focusCandidate
+                                            .getBottom());
+
+                    if (foundFullyContainedFocusable) {
+                        if (viewIsFullyContained && viewIsCloserToBoundary) {
+                            /*
+                             * We're dealing with only fully contained views, so
+                             * it has to be closer to the boundary to beat our
+                             * candidate
+                             */
+                            focusCandidate = view;
+                        }
+                    } else {
+                        if (viewIsFullyContained) {
+                            /* Any fully contained view beats a partially contained view */
+                            focusCandidate = view;
+                            foundFullyContainedFocusable = true;
+                        } else if (viewIsCloserToBoundary) {
+                            /*
+                             * Partially contained view beats another partially
+                             * contained view if it's closer
+                             */
+                            focusCandidate = view;
+                        }
+                    }
+                }
+            }
+        }
+
+        return focusCandidate;
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "page up/down" shortcut press. This
+     * method will scroll the view by one page up or down and give the focus
+     * to the topmost/bottommost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go one page up or
+     *                  {@link android.view.View#FOCUS_DOWN} to go one page down
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean pageScroll(int direction) {
+        boolean down = direction == View.FOCUS_DOWN;
+        int height = getHeight();
+
+        if (down) {
+            mTempRect.top = getScrollY() + height;
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                if (mTempRect.top + height > view.getBottom()) {
+                    mTempRect.top = view.getBottom() - height;
+                }
+            }
+        } else {
+            mTempRect.top = getScrollY() - height;
+            if (mTempRect.top < 0) {
+                mTempRect.top = 0;
+            }
+        }
+        mTempRect.bottom = mTempRect.top + height;
+
+        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+    }
+
+    /**
+     * <p>Handles scrolling in response to a "home/end" shortcut press. This
+     * method will scroll the view to the top or bottom and give the focus
+     * to the topmost/bottommost component in the new visible area. If no
+     * component is a good candidate for focus, this scrollview reclaims the
+     * focus.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go the top of the view or
+     *                  {@link android.view.View#FOCUS_DOWN} to go the bottom
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    public boolean fullScroll(int direction) {
+        boolean down = direction == View.FOCUS_DOWN;
+        int height = getHeight();
+
+        mTempRect.top = 0;
+        mTempRect.bottom = height;
+
+        if (down) {
+            int count = getChildCount();
+            if (count > 0) {
+                View view = getChildAt(count - 1);
+                mTempRect.bottom = view.getBottom() + getPaddingBottom();
+                mTempRect.top = mTempRect.bottom - height;
+            }
+        }
+
+        return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+    }
+
+    /**
+     * <p>Scrolls the view to make the area defined by <code>top</code> and
+     * <code>bottom</code> visible. This method attempts to give the focus
+     * to a component visible in this area. If no component can be focused in
+     * the new visible area, the focus is reclaimed by this ScrollView.</p>
+     *
+     * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+     *                  to go upward, {@link android.view.View#FOCUS_DOWN} to downward
+     * @param top       the top offset of the new area to be made visible
+     * @param bottom    the bottom offset of the new area to be made visible
+     * @return true if the key event is consumed by this method, false otherwise
+     */
+    private boolean scrollAndFocus(int direction, int top, int bottom) {
+        boolean handled = true;
+
+        int height = getHeight();
+        int containerTop = getScrollY();
+        int containerBottom = containerTop + height;
+        boolean up = direction == View.FOCUS_UP;
+
+        View newFocused = findFocusableViewInBounds(up, top, bottom);
+        if (newFocused == null) {
+            newFocused = this;
+        }
+
+        if (top >= containerTop && bottom <= containerBottom) {
+            handled = false;
+        } else {
+            int delta = up ? (top - containerTop) : (bottom - containerBottom);
+            doScrollY(delta);
+        }
+
+        if (newFocused != findFocus()) newFocused.requestFocus(direction);
+
+        return handled;
+    }
+
+    /**
+     * Handle scrolling in response to an up or down arrow click.
+     *
+     * @param direction The direction corresponding to the arrow key that was
+     *                  pressed
+     * @return True if we consumed the event, false otherwise
+     */
+    public boolean arrowScroll(int direction) {
+
+        View currentFocused = findFocus();
+        if (currentFocused == this) currentFocused = null;
+
+        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+
+        final int maxJump = getMaxScrollAmount();
+
+        if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
+            nextFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollY(scrollDelta);
+            nextFocused.requestFocus(direction);
+        } else {
+            // no new focus
+            int scrollDelta = maxJump;
+
+            if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {
+                scrollDelta = getScrollY();
+            } else if (direction == View.FOCUS_DOWN) {
+                if (getChildCount() > 0) {
+                    int daBottom = getChildAt(0).getBottom();
+                    int screenBottom = getScrollY() + getHeight() - getPaddingBottom();
+                    if (daBottom - screenBottom < maxJump) {
+                        scrollDelta = daBottom - screenBottom;
+                    }
+                }
+            }
+            if (scrollDelta == 0) {
+                return false;
+            }
+            doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);
+        }
+
+        if (currentFocused != null && currentFocused.isFocused()
+                && isOffScreen(currentFocused)) {
+            // previously focused item still has focus and is off screen, give
+            // it up (take it back to ourselves)
+            // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
+            // sure to
+            // get it)
+            final int descendantFocusability = getDescendantFocusability();  // save
+            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+            requestFocus();
+            setDescendantFocusability(descendantFocusability);  // restore
+        }
+        return true;
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is scrolled off
+     *  screen.
+     */
+    private boolean isOffScreen(View descendant) {
+        return !isWithinDeltaOfScreen(descendant, 0, getHeight());
+    }
+
+    /**
+     * @return whether the descendant of this scroll view is within delta
+     *  pixels of being on the screen.
+     */
+    private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
+        descendant.getDrawingRect(mTempRect);
+        offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+        return (mTempRect.bottom + delta) >= getScrollY()
+                && (mTempRect.top - delta) <= (getScrollY() + height);
+    }
+
+    /**
+     * Smooth scroll by a Y delta
+     *
+     * @param delta the number of pixels to scroll by on the Y axis
+     */
+    private void doScrollY(int delta) {
+        if (delta != 0) {
+            if (mSmoothScrollingEnabled) {
+                smoothScrollBy(0, delta);
+            } else {
+                scrollBy(0, delta);
+            }
+        }
+    }
+
+    /**
+     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+     *
+     * @param dx the number of pixels to scroll by on the X axis
+     * @param dy the number of pixels to scroll by on the Y axis
+     */
+    public final void smoothScrollBy(int dx, int dy) {
+        if (getChildCount() == 0) {
+            // Nothing to do.
+            return;
+        }
+        long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+        if (duration > ANIMATED_SCROLL_GAP) {
+            final int height = getHeight() - getPaddingBottom() - getPaddingTop();
+            final int bottom = getChildAt(0).getHeight();
+            final int maxY = Math.max(0, bottom - height);
+            final int scrollY = getScrollY();
+            dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
+
+            mScroller.startScroll(getScrollX(), scrollY, 0, dy);
+            ViewCompat.postInvalidateOnAnimation(this);
+        } else {
+            if (!mScroller.isFinished()) {
+                mScroller.abortAnimation();
+            }
+            scrollBy(dx, dy);
+        }
+        mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+    }
+
+    /**
+     * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+     *
+     * @param x the position where to scroll on the X axis
+     * @param y the position where to scroll on the Y axis
+     */
+    public final void smoothScrollTo(int x, int y) {
+        smoothScrollBy(x - getScrollX(), y - getScrollY());
+    }
+
+    /**
+     * <p>The scroll range of a scroll view is the overall height of all of its
+     * children.</p>
+     */
+    @Override
+    protected int computeVerticalScrollRange() {
+        final int count = getChildCount();
+        final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
+        if (count == 0) {
+            return contentHeight;
+        }
+
+        int scrollRange = getChildAt(0).getBottom();
+        final int scrollY = getScrollY();
+        final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
+        if (scrollY < 0) {
+            scrollRange -= scrollY;
+        } else if (scrollY > overscrollBottom) {
+            scrollRange += scrollY - overscrollBottom;
+        }
+
+        return scrollRange;
+    }
+
+    @Override
+    protected int computeVerticalScrollOffset() {
+        return Math.max(0, super.computeVerticalScrollOffset());
+    }
+
+    @Override
+    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+
+        int childWidthMeasureSpec;
+        int childHeightMeasureSpec;
+
+        childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft()
+                + getPaddingRight(), lp.width);
+
+        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
+                        + widthUsed, lp.width);
+        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            int oldX = getScrollX();
+            int oldY = getScrollY();
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+
+            if (oldX != x || oldY != y) {
+                final int range = getScrollRange();
+                final int overscrollMode = ViewCompat.getOverScrollMode(this);
+                final boolean canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+                        (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
+
+                overScrollByCompat(x - oldX, y - oldY, oldX, oldY, 0, range,
+                        0, 0, false);
+
+                if (canOverscroll) {
+                    ensureGlows();
+                    if (y <= 0 && oldY > 0) {
+                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
+                    } else if (y >= range && oldY < range) {
+                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Scrolls the view to the given child.
+     *
+     * @param child the View to scroll to
+     */
+    private void scrollToChild(View child) {
+        child.getDrawingRect(mTempRect);
+
+        /* Offset from child's local coordinates to ScrollView coordinates */
+        offsetDescendantRectToMyCoords(child, mTempRect);
+
+        int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+
+        if (scrollDelta != 0) {
+            scrollBy(0, scrollDelta);
+        }
+    }
+
+    /**
+     * If rect is off screen, scroll just enough to get it (or at least the
+     * first screen size chunk of it) on screen.
+     *
+     * @param rect      The rectangle.
+     * @param immediate True to scroll immediately without animation
+     * @return true if scrolling was performed
+     */
+    private boolean scrollToChildRect(Rect rect, boolean immediate) {
+        final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
+        final boolean scroll = delta != 0;
+        if (scroll) {
+            if (immediate) {
+                scrollBy(0, delta);
+            } else {
+                smoothScrollBy(0, delta);
+            }
+        }
+        return scroll;
+    }
+
+    /**
+     * Compute the amount to scroll in the Y direction in order to get
+     * a rectangle completely on the screen (or, if taller than the screen,
+     * at least the first screen size chunk of it).
+     *
+     * @param rect The rect.
+     * @return The scroll delta.
+     */
+    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
+        if (getChildCount() == 0) return 0;
+
+        int height = getHeight();
+        int screenTop = getScrollY();
+        int screenBottom = screenTop + height;
+
+        int fadingEdge = getVerticalFadingEdgeLength();
+
+        // leave room for top fading edge as long as rect isn't at very top
+        if (rect.top > 0) {
+            screenTop += fadingEdge;
+        }
+
+        // leave room for bottom fading edge as long as rect isn't at very bottom
+        if (rect.bottom < getChildAt(0).getHeight()) {
+            screenBottom -= fadingEdge;
+        }
+
+        int scrollYDelta = 0;
+
+        if (rect.bottom > screenBottom && rect.top > screenTop) {
+            // need to move down to get it in view: move down just enough so
+            // that the entire rectangle is in view (or at least the first
+            // screen size chunk).
+
+            if (rect.height() > height) {
+                // just enough to get screen size chunk on
+                scrollYDelta += (rect.top - screenTop);
+            } else {
+                // get entire rect at bottom of screen
+                scrollYDelta += (rect.bottom - screenBottom);
+            }
+
+            // make sure we aren't scrolling beyond the end of our content
+            int bottom = getChildAt(0).getBottom();
+            int distanceToBottom = bottom - screenBottom;
+            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+
+        } else if (rect.top < screenTop && rect.bottom < screenBottom) {
+            // need to move up to get it in view: move up just enough so that
+            // entire rectangle is in view (or at least the first screen
+            // size chunk of it).
+
+            if (rect.height() > height) {
+                // screen size chunk
+                scrollYDelta -= (screenBottom - rect.bottom);
+            } else {
+                // entire rect at top
+                scrollYDelta -= (screenTop - rect.top);
+            }
+
+            // make sure we aren't scrolling any further than the top our content
+            scrollYDelta = Math.max(scrollYDelta, -getScrollY());
+        }
+        return scrollYDelta;
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (!mIsLayoutDirty) {
+            scrollToChild(focused);
+        } else {
+            // The child may not be laid out yet, we can't compute the scroll yet
+            mChildToScrollTo = focused;
+        }
+        super.requestChildFocus(child, focused);
+    }
+
+
+    /**
+     * When looking for focus in children of a scroll view, need to be a little
+     * more careful not to give focus to something that is scrolled off screen.
+     *
+     * This is more expensive than the default {@link android.view.ViewGroup}
+     * implementation, otherwise this behavior might have been made the default.
+     */
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+
+        // convert from forward / backward notation to up / down / left / right
+        // (ugh).
+        if (direction == View.FOCUS_FORWARD) {
+            direction = View.FOCUS_DOWN;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            direction = View.FOCUS_UP;
+        }
+
+        final View nextFocus = previouslyFocusedRect == null ?
+                FocusFinder.getInstance().findNextFocus(this, null, direction) :
+                FocusFinder.getInstance().findNextFocusFromRect(this,
+                        previouslyFocusedRect, direction);
+
+        if (nextFocus == null) {
+            return false;
+        }
+
+        if (isOffScreen(nextFocus)) {
+            return false;
+        }
+
+        return nextFocus.requestFocus(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+            boolean immediate) {
+        // offset into coordinate space of this scroll view
+        rectangle.offset(child.getLeft() - child.getScrollX(),
+                child.getTop() - child.getScrollY());
+
+        return scrollToChildRect(rectangle, immediate);
+    }
+
+    @Override
+    public void requestLayout() {
+        mIsLayoutDirty = true;
+        super.requestLayout();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mIsLayoutDirty = false;
+        // Give a child focus if it needs it
+        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+            scrollToChild(mChildToScrollTo);
+        }
+        mChildToScrollTo = null;
+
+        if (!mIsLaidOut) {
+            if (mSavedState != null) {
+                scrollTo(getScrollX(), mSavedState.scrollPosition);
+                mSavedState = null;
+            } // mScrollY default value is "0"
+
+            final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;
+            final int scrollRange = Math.max(0,
+                    childHeight - (b - t - getPaddingBottom() - getPaddingTop()));
+
+            // Don't forget to clamp
+            if (getScrollY() > scrollRange) {
+                scrollTo(getScrollX(), scrollRange);
+            } else if (getScrollY() < 0) {
+                scrollTo(getScrollX(), 0);
+            }
+        }
+
+        // Calling this with the present values causes it to re-claim them
+        scrollTo(getScrollX(), getScrollY());
+        mIsLaidOut = true;
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        mIsLaidOut = false;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        View currentFocused = findFocus();
+        if (null == currentFocused || this == currentFocused)
+            return;
+
+        // If the currently-focused view was visible on the screen when the
+        // screen was at the old height, then scroll the screen to make that
+        // view visible with the new screen height.
+        if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
+            currentFocused.getDrawingRect(mTempRect);
+            offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+            int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+            doScrollY(scrollDelta);
+        }
+    }
+
+    /**
+     * Return true if child is a descendant of parent, (or equal to the parent).
+     */
+    private static boolean isViewDescendantOf(View child, View parent) {
+        if (child == parent) {
+            return true;
+        }
+
+        final ViewParent theParent = child.getParent();
+        return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+    }
+
+    /**
+     * Fling the scroll view
+     *
+     * @param velocityY The initial velocity in the Y direction. Positive
+     *                  numbers mean that the finger/cursor is moving down the screen,
+     *                  which means we want to scroll towards the top.
+     */
+    public void fling(int velocityY) {
+        if (getChildCount() > 0) {
+            int height = getHeight() - getPaddingBottom() - getPaddingTop();
+            int bottom = getChildAt(0).getHeight();
+
+            mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,
+                    Math.max(0, bottom - height), 0, height/2);
+
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    private void flingWithNestedDispatch(int velocityY) {
+        final int scrollY = getScrollY();
+        final boolean canFling = (scrollY > 0 || velocityY > 0) &&
+                (scrollY < getScrollRange() || velocityY < 0);
+        if (!dispatchNestedPreFling(0, velocityY)) {
+            dispatchNestedFling(0, velocityY, canFling);
+            if (canFling) {
+                fling(velocityY);
+            }
+        }
+    }
+
+    private void endDrag() {
+        mIsBeingDragged = false;
+
+        recycleVelocityTracker();
+
+        if (mEdgeGlowTop != null) {
+            mEdgeGlowTop.onRelease();
+            mEdgeGlowBottom.onRelease();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This version also clamps the scrolling to the bounds of our child.
+     */
+    @Override
+    public void scrollTo(int x, int y) {
+        // we rely on the fact the View.scrollBy calls scrollTo.
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
+            y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
+            if (x != getScrollX() || y != getScrollY()) {
+                super.scrollTo(x, y);
+            }
+        }
+    }
+
+    private void ensureGlows() {
+        if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
+            if (mEdgeGlowTop == null) {
+                Context context = getContext();
+                mEdgeGlowTop = new EdgeEffectCompat(context);
+                mEdgeGlowBottom = new EdgeEffectCompat(context);
+            }
+        } else {
+            mEdgeGlowTop = null;
+            mEdgeGlowBottom = null;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (mEdgeGlowTop != null) {
+            final int scrollY = getScrollY();
+            if (!mEdgeGlowTop.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+
+                canvas.translate(getPaddingLeft(), Math.min(0, scrollY));
+                mEdgeGlowTop.setSize(width, getHeight());
+                if (mEdgeGlowTop.draw(canvas)) {
+                    ViewCompat.postInvalidateOnAnimation(this);
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mEdgeGlowBottom.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+                final int height = getHeight();
+
+                canvas.translate(-width + getPaddingLeft(),
+                        Math.max(getScrollRange(), scrollY) + height);
+                canvas.rotate(180, width, 0);
+                mEdgeGlowBottom.setSize(width, height);
+                if (mEdgeGlowBottom.draw(canvas)) {
+                    ViewCompat.postInvalidateOnAnimation(this);
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+        }
+    }
+
+    private static int clamp(int n, int my, int child) {
+        if (my >= child || n < 0) {
+            /* my >= child is this case:
+             *                    |--------------- me ---------------|
+             *     |------ child ------|
+             * or
+             *     |--------------- me ---------------|
+             *            |------ child ------|
+             * or
+             *     |--------------- me ---------------|
+             *                                  |------ child ------|
+             *
+             * n < 0 is this case:
+             *     |------ me ------|
+             *                    |-------- child --------|
+             *     |-- mScrollX --|
+             */
+            return 0;
+        }
+        if ((my+n) > child) {
+            /* this case:
+             *                    |------ me ------|
+             *     |------ child ------|
+             *     |-- mScrollX --|
+             */
+            return child-my;
+        }
+        return n;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        mSavedState = ss;
+        requestLayout();
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.scrollPosition = getScrollY();
+        return ss;
+    }
+
+    static class SavedState extends BaseSavedState {
+        public int scrollPosition;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public SavedState(Parcel source) {
+            super(source);
+            scrollPosition = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(scrollPosition);
+        }
+
+        @Override
+        public String toString() {
+            return "HorizontalScrollView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " scrollPosition=" + scrollPosition + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    static class AccessibilityDelegate extends AccessibilityDelegateCompat {
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
+            if (super.performAccessibilityAction(host, action, arguments)) {
+                return true;
+            }
+            final NestedScrollView nsvHost = (NestedScrollView) host;
+            if (!nsvHost.isEnabled()) {
+                return false;
+            }
+            switch (action) {
+                case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
+                    final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+                            - nsvHost.getPaddingTop();
+                    final int targetScrollY = Math.min(nsvHost.getScrollY() + viewportHeight,
+                            nsvHost.getScrollRange());
+                    if (targetScrollY != nsvHost.getScrollY()) {
+                        nsvHost.smoothScrollTo(0, targetScrollY);
+                        return true;
+                    }
+                }
+                return false;
+                case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
+                    final int viewportHeight = nsvHost.getHeight() - nsvHost.getPaddingBottom()
+                            - nsvHost.getPaddingTop();
+                    final int targetScrollY = Math.max(nsvHost.getScrollY() - viewportHeight, 0);
+                    if (targetScrollY != nsvHost.getScrollY()) {
+                        nsvHost.smoothScrollTo(0, targetScrollY);
+                        return true;
+                    }
+                }
+                return false;
+            }
+            return false;
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            final NestedScrollView nsvHost = (NestedScrollView) host;
+            info.setClassName(ScrollView.class.getName());
+            if (nsvHost.isEnabled()) {
+                final int scrollRange = nsvHost.getScrollRange();
+                if (scrollRange > 0) {
+                    info.setScrollable(true);
+                    if (nsvHost.getScrollY() > 0) {
+                        info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+                    }
+                    if (nsvHost.getScrollY() < scrollRange) {
+                        info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(host, event);
+            final NestedScrollView nsvHost = (NestedScrollView) host;
+            event.setClassName(ScrollView.class.getName());
+            final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
+            final boolean scrollable = nsvHost.getScrollRange() > 0;
+            record.setScrollable(scrollable);
+            record.setScrollX(nsvHost.getScrollX());
+            record.setScrollY(nsvHost.getScrollY());
+            record.setMaxScrollX(nsvHost.getScrollX());
+            record.setMaxScrollY(nsvHost.getScrollRange());
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/widget/SwipeProgressBar.java b/v4/java/android/support/v4/widget/SwipeProgressBar.java
index ba60d89..2314a5e 100644
--- a/v4/java/android/support/v4/widget/SwipeProgressBar.java
+++ b/v4/java/android/support/v4/widget/SwipeProgressBar.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -48,7 +49,7 @@
     private static final int FINISH_ANIMATION_DURATION_MS = 1000;
 
     // Interpolator for varying the speed of the animation.
-    private static final Interpolator INTERPOLATOR = BakedBezierInterpolator.getInstance();
+    private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
 
     private final Paint mPaint = new Paint();
     private final RectF mClipRect = new RectF();
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index 2496ef9..55f2ce2 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ScrollingView;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -101,6 +102,7 @@
     private boolean mOriginalOffsetCalculated = false;
 
     private float mInitialMotionY;
+    private float mInitialDownY;
     private boolean mIsBeingDragged;
     private int mActivePointerId = INVALID_POINTER;
     // Whether this item is scaled up rather than clipped
@@ -446,13 +448,30 @@
     }
 
     /**
+     * @deprecated Use {@link #setProgressBackgroundColorSchemeResource(int)}
+     */
+    @Deprecated
+    public void setProgressBackgroundColor(int colorRes) {
+        setProgressBackgroundColorSchemeResource(colorRes);
+    }
+
+    /**
      * Set the background color of the progress spinner disc.
      *
      * @param colorRes Resource id of the color.
      */
-    public void setProgressBackgroundColor(int colorRes) {
-        mCircleView.setBackgroundColor(colorRes);
-        mProgress.setBackgroundColor(getResources().getColor(colorRes));
+    public void setProgressBackgroundColorSchemeResource(int colorRes) {
+        setProgressBackgroundColorSchemeColor(getResources().getColor(colorRes));
+    }
+
+    /**
+     * Set the background color of the progress spinner disc.
+     *
+     * @param color
+     */
+    public void setProgressBackgroundColorSchemeColor(int color) {
+        mCircleView.setBackgroundColor(color);
+        mProgress.setBackgroundColor(color);
     }
 
     /**
@@ -577,6 +596,17 @@
     }
 
     /**
+     * Get the diameter of the progress circle that is displayed as part of the
+     * swipe to refresh layout. This is not valid until a measure pass has
+     * completed.
+     *
+     * @return Diameter in pixels of the progress circle view.
+     */
+    public int getProgressCircleDiameter() {
+        return mCircleView != null ?mCircleView.getMeasuredHeight() : 0;
+    }
+
+    /**
      * @return Whether it is possible for the child view of this layout to
      *         scroll up. Override this if the child view is a custom view.
      */
@@ -588,7 +618,7 @@
                         && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                                 .getTop() < absListView.getPaddingTop());
             } else {
-                return mTarget.getScrollY() > 0;
+                return ViewCompat.canScrollVertically(mTarget, -1) || mTarget.getScrollY() > 0;
             }
         } else {
             return ViewCompat.canScrollVertically(mTarget, -1);
@@ -615,11 +645,12 @@
                 setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
                 mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                 mIsBeingDragged = false;
-                final float initialMotionY = getMotionEventY(ev, mActivePointerId);
-                if (initialMotionY == -1) {
+                final float initialDownY = getMotionEventY(ev, mActivePointerId);
+                if (initialDownY == -1) {
                     return false;
                 }
-                mInitialMotionY = initialMotionY;
+                mInitialDownY = initialDownY;
+                break;
 
             case MotionEvent.ACTION_MOVE:
                 if (mActivePointerId == INVALID_POINTER) {
@@ -631,8 +662,9 @@
                 if (y == -1) {
                     return false;
                 }
-                final float yDiff = y - mInitialMotionY;
+                final float yDiff = y - mInitialDownY;
                 if (yDiff > mTouchSlop && !mIsBeingDragged) {
+                    mInitialMotionY = mInitialDownY + mTouchSlop;
                     mIsBeingDragged = true;
                     mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
                 }
@@ -733,7 +765,7 @@
                             // Animate the alpha
                             startProgressAlphaStartAnimation();
                         }
-                        float strokeStart = (float) (adjustedPercent * .8f);
+                        float strokeStart = adjustedPercent * .8f;
                         mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
                         mProgress.setArrowScale(Math.min(1f, adjustedPercent));
                     } else {
@@ -852,6 +884,7 @@
             targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
             int offset = targetTop - mCircleView.getTop();
             setTargetOffsetTopAndBottom(offset, false /* requires update */);
+            mProgress.setArrowScale(1 - interpolatedTime);
         }
     };
 
@@ -920,4 +953,4 @@
     public interface OnRefreshListener {
         public void onRefresh();
     }
-}
\ No newline at end of file
+}
diff --git a/v4/java/android/support/v4/widget/TextViewCompat.java b/v4/java/android/support/v4/widget/TextViewCompat.java
new file mode 100644
index 0000000..c31196a
--- /dev/null
+++ b/v4/java/android/support/v4/widget/TextViewCompat.java
@@ -0,0 +1,203 @@
+/*
+ * 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.v4.widget;
+
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.widget.TextView;
+
+/**
+ * Helper for accessing features in {@link TextView} introduced after API level
+ * 4 in a backwards compatible fashion.
+ */
+public class TextViewCompat {
+
+    // Hide constructor
+    private TextViewCompat() {
+    }
+
+    interface TextViewCompatImpl {
+
+        public void setCompoundDrawablesRelative(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom);
+
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom);
+
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                int start, int top, int end, int bottom);
+
+    }
+
+    static class BaseTextViewCompatImpl implements TextViewCompatImpl {
+
+        @Override
+        public void setCompoundDrawablesRelative(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            textView.setCompoundDrawables(start, top, end, bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                int start, int top, int end, int bottom) {
+            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+        }
+
+    }
+
+    static class JbMr1TextViewCompatImpl extends BaseTextViewCompatImpl {
+
+        @Override
+        public void setCompoundDrawablesRelative(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            TextViewCompatJbMr1.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            TextViewCompatJbMr1.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
+                    start, top, end, bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                int start, int top, int end, int bottom) {
+            TextViewCompatJbMr1.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
+                    start, top, end, bottom);
+        }
+
+    }
+
+    static class JbMr2TextViewCompatImpl extends JbMr1TextViewCompatImpl {
+
+        @Override
+        public void setCompoundDrawablesRelative(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            TextViewCompatJbMr2.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+                @Nullable Drawable bottom) {
+            TextViewCompatJbMr2
+                    .setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end,
+                            bottom);
+        }
+
+        @Override
+        public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+                int start, int top, int end, int bottom) {
+            TextViewCompatJbMr2.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
+                    start, top, end, bottom);
+        }
+    }
+
+    static final TextViewCompatImpl IMPL;
+
+    static {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 18) {
+            IMPL = new JbMr2TextViewCompatImpl();
+        } else if (version >= 17) {
+            IMPL = new JbMr1TextViewCompatImpl();
+        } else {
+            IMPL = new BaseTextViewCompatImpl();
+        }
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the start of, above, to the end
+     * of, and below the text. Use {@code null} if you do not want a Drawable
+     * there. The Drawables must already have had {@link Drawable#setBounds}
+     * called.
+     * <p/>
+     * Calling this method will overwrite any Drawables previously set using
+     * {@link TextView#setCompoundDrawables} or related methods.
+     *
+     * @param textView The TextView against which to invoke the method.
+     * @attr ref android.R.styleable#TextView_drawableStart
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableEnd
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public static void setCompoundDrawablesRelative(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        IMPL.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the start of, above, to the end
+     * of, and below the text. Use {@code null} if you do not want a Drawable
+     * there. The Drawables' bounds will be set to their intrinsic bounds.
+     * <p/>
+     * Calling this method will overwrite any Drawables previously set using
+     * {@link TextView#setCompoundDrawables} or related methods.
+     *
+     * @param textView The TextView against which to invoke the method.
+     * @attr ref android.R.styleable#TextView_drawableStart
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableEnd
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
+    }
+
+    /**
+     * Sets the Drawables (if any) to appear to the start of, above, to the end
+     * of, and below the text. Use 0 if you do not want a Drawable there. The
+     * Drawables' bounds will be set to their intrinsic bounds.
+     * <p/>
+     * Calling this method will overwrite any Drawables previously set using
+     * {@link TextView#setCompoundDrawables} or related methods.
+     *
+     * @param textView The TextView against which to invoke the method.
+     * @param start    Resource identifier of the start Drawable.
+     * @param top      Resource identifier of the top Drawable.
+     * @param end      Resource identifier of the end Drawable.
+     * @param bottom   Resource identifier of the bottom Drawable.
+     * @attr ref android.R.styleable#TextView_drawableStart
+     * @attr ref android.R.styleable#TextView_drawableTop
+     * @attr ref android.R.styleable#TextView_drawableEnd
+     * @attr ref android.R.styleable#TextView_drawableBottom
+     */
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            int start, int top, int end, int bottom) {
+        IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
+    }
+
+}
diff --git a/v4/java/android/support/v4/widget/ViewDragHelper.java b/v4/java/android/support/v4/widget/ViewDragHelper.java
index 2112b23..c6bebd3 100644
--- a/v4/java/android/support/v4/widget/ViewDragHelper.java
+++ b/v4/java/android/support/v4/widget/ViewDragHelper.java
@@ -868,6 +868,7 @@
     }
 
     void setDragState(int state) {
+        mParentView.removeCallbacks(mSetIdleRunnable);
         if (mDragState != state) {
             mDragState = state;
             mCallback.onViewDragStateChanged(state);
diff --git a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
index 6832b42..4865a8b 100644
--- a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
+++ b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
@@ -59,4 +59,8 @@
     public static int getWindowSystemUiVisibility(View view) {
         return view.getWindowSystemUiVisibility();
     }
+
+    public static boolean isPaddingRelative(View view) {
+        return view.isPaddingRelative();
+    }
 }
diff --git a/v4/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java b/v4/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java
new file mode 100644
index 0000000..7c073c6
--- /dev/null
+++ b/v4/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java
@@ -0,0 +1,49 @@
+/*
+ * 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.v4.widget;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.widget.TextView;
+
+class TextViewCompatJbMr1 {
+
+    public static void setCompoundDrawablesRelative(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        textView.setCompoundDrawables(rtl ? end : start, top, rtl ? start : end, bottom);
+    }
+
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top, rtl ? start : end,
+                bottom);
+    }
+
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            int start, int top, int end, int bottom) {
+        boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        textView.setCompoundDrawablesWithIntrinsicBounds(rtl ? end : start, top, rtl ? start : end,
+                bottom);
+    }
+
+}
diff --git a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
new file mode 100644
index 0000000..48e2c48
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.RemoteControlClient;
+import android.os.SystemClock;
+
+public class MediaSessionCompatApi18 {
+
+    public static Object createPlaybackPositionUpdateListener(
+            MediaSessionCompatApi14.Callback callback) {
+        return new OnPlaybackPositionUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+    }
+
+    public static void registerMediaButtonEventReceiver(Context context, PendingIntent pi) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.registerMediaButtonEventReceiver(pi);
+    }
+
+    public static void unregisterMediaButtonEventReceiver(Context context, PendingIntent pi) {
+        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        am.unregisterMediaButtonEventReceiver(pi);
+    }
+
+    public static void setState(Object rccObj, int state, long position, float speed,
+            long updateTime) {
+        long currTime = SystemClock.elapsedRealtime();
+        if (state == MediaSessionCompatApi14.STATE_PLAYING && position > 0) {
+            long diff = 0;
+            if (updateTime > 0) {
+                diff = currTime - updateTime;
+                if (speed > 0 && speed != 1f) {
+                    diff *= speed;
+                }
+            }
+            position += diff;
+        }
+        state = MediaSessionCompatApi14.getRccStateFromState(state);
+        ((RemoteControlClient) rccObj).setPlaybackState(state, position, speed);
+    }
+
+    public static void setOnPlaybackPositionUpdateListener(Object rccObj,
+            Object onPositionUpdateObj) {
+        ((RemoteControlClient) rccObj).setPlaybackPositionUpdateListener(
+                (RemoteControlClient.OnPlaybackPositionUpdateListener) onPositionUpdateObj);
+    }
+
+    static class OnPlaybackPositionUpdateListener<T extends MediaSessionCompatApi14.Callback>
+            implements RemoteControlClient.OnPlaybackPositionUpdateListener {
+        protected final T mCallback;
+
+        public OnPlaybackPositionUpdateListener(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onPlaybackPositionUpdate(long newPositionMs) {
+            mCallback.onSeekTo(newPositionMs);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
new file mode 100644
index 0000000..ec9fe61
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
@@ -0,0 +1,43 @@
+/*
+ * 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.v4.widget;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.widget.TextView;
+
+class TextViewCompatJbMr2 {
+
+    public static void setCompoundDrawablesRelative(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        textView.setCompoundDrawablesRelative(start, top, end, bottom);
+    }
+
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            @Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
+            @Nullable Drawable bottom) {
+        textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+    }
+
+    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
+            int start, int top, int end, int bottom) {
+        textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
+    }
+
+}
diff --git a/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java b/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
index 9767c27..b42c86b 100644
--- a/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.graphics.drawable;
 
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 
 /**
@@ -29,4 +31,11 @@
     public static boolean isAutoMirrored(Drawable drawable) {
         return drawable.isAutoMirrored();
     }
+
+    public static Drawable wrapForTinting(Drawable drawable) {
+        if (!(drawable instanceof DrawableWrapperKitKat)) {
+            return new DrawableWrapperKitKat(drawable);
+        }
+        return drawable;
+    }
 }
diff --git a/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
new file mode 100644
index 0000000..dc70c6e
--- /dev/null
+++ b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
@@ -0,0 +1,36 @@
+/*
+ * 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.v4.graphics.drawable;
+
+import android.graphics.drawable.Drawable;
+
+class DrawableWrapperKitKat extends DrawableWrapperHoneycomb {
+
+    DrawableWrapperKitKat(Drawable drawable) {
+        super(drawable);
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mDrawable.setAutoMirrored(mirrored);
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mDrawable.isAutoMirrored();
+    }
+}
diff --git a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
new file mode 100644
index 0000000..261f4ca
--- /dev/null
+++ b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.graphics.Bitmap;
+import android.media.MediaMetadataEditor;
+import android.media.Rating;
+import android.media.RemoteControlClient;
+import android.os.Bundle;
+
+public class MediaSessionCompatApi19 {
+    /***** MediaMetadata keys ********/
+    private static final String METADATA_KEY_ART = "android.media.metadata.ART";
+    private static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+    private static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+    private static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+    public static Object createMetadataUpdateListener(MediaSessionCompatApi14.Callback callback) {
+        return new OnMetadataUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+    }
+
+    public static void setMetadata(Object rccObj, Bundle metadata, boolean supportRating) {
+        RemoteControlClient.MetadataEditor editor = ((RemoteControlClient) rccObj).editMetadata(
+                true);
+        MediaSessionCompatApi14.buildOldMetadata(metadata, editor);
+        addNewMetadata(metadata, editor);
+        if (supportRating && android.os.Build.VERSION.SDK_INT > 19) {
+            editor.addEditableKey(RemoteControlClient.MetadataEditor.RATING_KEY_BY_USER);
+        }
+        editor.apply();
+    }
+
+    public static void setOnMetadataUpdateListener(Object rccObj, Object onMetadataUpdateObj) {
+        ((RemoteControlClient) rccObj).setMetadataUpdateListener(
+                (RemoteControlClient.OnMetadataUpdateListener) onMetadataUpdateObj);
+    }
+
+    static void addNewMetadata(Bundle metadata, RemoteControlClient.MetadataEditor editor) {
+        if (metadata.containsKey(METADATA_KEY_RATING)) {
+            editor.putObject(MediaMetadataEditor.RATING_KEY_BY_OTHERS,
+                    metadata.getParcelable(METADATA_KEY_RATING));
+        }
+        if (metadata.containsKey(METADATA_KEY_USER_RATING)) {
+            editor.putObject(MediaMetadataEditor.RATING_KEY_BY_USER,
+                    metadata.getParcelable(METADATA_KEY_USER_RATING));
+        }
+        if (metadata.containsKey(METADATA_KEY_ART)) {
+            Bitmap art = metadata.getParcelable(METADATA_KEY_ART);
+            editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, art);
+        } else if (metadata.containsKey(METADATA_KEY_ALBUM_ART)) {
+            // Fall back to album art if the track art wasn't available
+            Bitmap art = metadata.getParcelable(METADATA_KEY_ALBUM_ART);
+            editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, art);
+        }
+    }
+
+    static class OnMetadataUpdateListener<T extends MediaSessionCompatApi14.Callback> implements
+            RemoteControlClient.OnMetadataUpdateListener {
+        protected final T mCallback;
+
+        public OnMetadataUpdateListener(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onMetadataUpdate(int key, Object newValue) {
+            if (key == MediaMetadataEditor.RATING_KEY_BY_USER && newValue instanceof Rating) {
+                mCallback.onSetRating(newValue);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index cbc4d2b..b827b1f 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -302,7 +302,7 @@
 
         PrintDocumentAdapter printDocumentAdapter = new PrintDocumentAdapter() {
             private PrintAttributes mAttributes;
-            AsyncTask<Uri, Boolean, Bitmap> loadBitmap;
+            AsyncTask<Uri, Boolean, Bitmap> mLoadBitmap;
             Bitmap mBitmap = null;
 
             @Override
@@ -311,9 +311,11 @@
                                  final CancellationSignal cancellationSignal,
                                  final LayoutResultCallback layoutResultCallback,
                                  Bundle bundle) {
+
+                mAttributes = newPrintAttributes;
+
                 if (cancellationSignal.isCanceled()) {
                     layoutResultCallback.onLayoutCancelled();
-                    mAttributes = newPrintAttributes;
                     return;
                 }
                 // we finished the load
@@ -327,7 +329,7 @@
                     return;
                 }
 
-                loadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
+                mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
 
                     @Override
                     protected void onPreExecute() {
@@ -368,17 +370,16 @@
                         } else {
                             layoutResultCallback.onLayoutFailed(null);
                         }
+                        mLoadBitmap = null;
                     }
 
                     @Override
                     protected void onCancelled(Bitmap result) {
                         // Task was cancelled, report that.
                         layoutResultCallback.onLayoutCancelled();
+                        mLoadBitmap = null;
                     }
-                };
-                loadBitmap.execute();
-
-                mAttributes = newPrintAttributes;
+                }.execute();
             }
 
             private void cancelLoad() {
@@ -394,7 +395,9 @@
             public void onFinish() {
                 super.onFinish();
                 cancelLoad();
-                loadBitmap.cancel(true);
+                if (mLoadBitmap != null) {
+                    mLoadBitmap.cancel(true);
+                }
                 if (callback != null) {
                     callback.onFinish();
                 }
diff --git a/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java b/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
index bbafe4c..eb067bc 100644
--- a/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
@@ -21,7 +21,7 @@
 /**
  * KitKat-specific View API implementation.
  */
-public class ViewCompatKitKat {
+class ViewCompatKitKat {
     public static int getAccessibilityLiveRegion(View view) {
         return view.getAccessibilityLiveRegion();
     }
@@ -29,4 +29,8 @@
     public static void setAccessibilityLiveRegion(View view, int mode) {
         view.setAccessibilityLiveRegion(mode);
     }
+
+    public static boolean isLaidOut(View view) {
+        return view.isLaidOut();
+    }
 }
diff --git a/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml b/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml
new file mode 100644
index 0000000..79d02d4
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_grow_fade_in_from_bottom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT 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:shareInterpolator="false">
+    <scale 	android:interpolator="@android:anim/decelerate_interpolator"
+              android:fromXScale="0.9" android:toXScale="1.0"
+              android:fromYScale="0.9" android:toYScale="1.0"
+              android:pivotX="50%" android:pivotY="100%"
+              android:duration="@integer/abc_config_activityDefaultDur" />
+    <alpha 	android:interpolator="@android:anim/decelerate_interpolator"
+              android:fromAlpha="0.0" android:toAlpha="1.0"
+              android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/anim/abc_popup_enter.xml b/v7/appcompat/res/anim/abc_popup_enter.xml
new file mode 100644
index 0000000..91664da
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_popup_enter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false" >
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/anim/abc_popup_exit.xml b/v7/appcompat/res/anim/abc_popup_exit.xml
new file mode 100644
index 0000000..db7e807
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_popup_exit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:shareInterpolator="false" >
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml b/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml
new file mode 100644
index 0000000..9a23cd2
--- /dev/null
+++ b/v7/appcompat/res/anim/abc_shrink_fade_out_from_bottom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+    <scale 	android:interpolator="@android:anim/decelerate_interpolator"
+              android:fromXScale="1.0" android:toXScale="0.9"
+              android:fromYScale="1.0" android:toYScale="0.9"
+              android:pivotX="50%" android:pivotY="100%"
+              android:duration="@integer/abc_config_activityDefaultDur" />
+    <alpha 	android:interpolator="@android:anim/decelerate_interpolator"
+              android:fromAlpha="1.0" android:toAlpha="0.0"
+              android:duration="@integer/abc_config_activityShortDur" />
+</set>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/switch_thumb_material_dark.xml b/v7/appcompat/res/color/switch_thumb_material_dark.xml
new file mode 100644
index 0000000..6153382
--- /dev/null
+++ b/v7/appcompat/res/color/switch_thumb_material_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/switch_thumb_disabled_material_dark"/>
+    <item android:color="@color/switch_thumb_normal_material_dark"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/switch_thumb_material_light.xml b/v7/appcompat/res/color/switch_thumb_material_light.xml
new file mode 100644
index 0000000..94d7114
--- /dev/null
+++ b/v7/appcompat/res/color/switch_thumb_material_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/switch_thumb_disabled_material_light"/>
+    <item android:color="@color/switch_thumb_normal_material_light"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index 6c14157..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index f4ff16b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..4d9f861
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
index 7a9e9bd..9911008 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
index 874edbf..69ff9dd 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
index 0d3e1e7..9218981 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
index a8c390e..a588576 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..b184dbc
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..6549c52
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 8e7b62f..705e0d9 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index adcb9e9..b692710 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png
index e51ef28..2264398 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 6c36eae..f61e8e3 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
index 82459ea..0fd1556 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png
index 47263ea..65ccd8f 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
index aa23c591..b9ff1db 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 03b1aac..70eb073 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
index 4c17541..e78bcaf 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
index 675f3ee9..9a87820 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
index a30dc06..8610c50 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
index 413b220..2d971a9 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
index 0eacedd..ee40812 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
index f7382d3..b9baa0c 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
index eefd59e..a87d2cd 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png
index 2fa6d7e..1e571f5 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_divider_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png
index 5552708..c09ec90 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_focused_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png
index 4ea7afa..62fbd2c 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_longpressed_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
index 596accb..2f6ef91 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
index 2054530..863ce95f 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png
index f6fd30d..b6d4677 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png
index ca8e9a2..e01c739 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index 76a5c53..a6b3696 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png
index 385734e..9d8451a 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
index de7ac29..9de0263 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
index 0ebe65e..56436a1 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png
index 21b2135..4b0b10a 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png
new file mode 100644
index 0000000..5e0bf84
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_text_cursor_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png
index b9a81be..5b13bc1 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
index 3682629..0078bf6 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png
index ce577e5..a74ab26 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png
index 7c305ab..6282df4 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
index dcdd03b..2e1062f 100644
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 5338f02..a262b0c 100644
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
index fd27a0f..9ed43ca 100644
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
index d6e0b99..4cd8a27 100644
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 482e142..e300b7c 100644
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 5aaad7eb..05b1e11 100644
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
index c0246b3..aa7b323 100644
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
index 74160c3..d02a5da 100644
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 753496a..a188f2f 100644
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 8a4e22e..e95ba94 100644
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
index 6944267..87bf8d3 100644
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
index 2d633346..b097e48 100644
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 2b308bf..de37158 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 9b5be20..ac86165 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
index 07d0a5d..8b2adf6 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
index bd1029d..0b89504 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 33f6587..7dc6934 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index a5015c6..884cd12 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
index 2f12fc0..90fe333 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
index b164173..930630d 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index ed4ba34..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index 8f10bd5..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..fa0ed8f
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
index 70793c4..7a9fcbc 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
index 8aa1be2..3b052e5 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
index 54ef480..96a8693 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
index 4f8a162..827d634 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..0908475
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..a5a437f
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 03d3dfb..b2191da 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 6635830..2a94e6e 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png
index ae8cccd..038e000 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 6674351..8043d4c 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
index bbc43b1..e80681a 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
index 42ac8ca..9603e76 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
index b5f6176..44c1423 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 6aa238c..80c0695 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
index aa4f1c2..3966d6ad 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
index 1d8ad18..017e45e 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
index d40353c..ec0cff4 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
index 488d1ab..966938b 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
index e0d5ac4..d05f969 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
index 0fb57b2..451818c 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
index fca776f..a216da1 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png
index 070bdbf..1e571f5 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_divider_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png
index 00f05d8..addb54a 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_focused_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png
index 3bf8e036..5fcd5b2 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_longpressed_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
index fd0e8d7..251b989 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
index 061904c..01efec0 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png
index 92da2f0..f1d1b61 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png
index 42cb646..10851f6 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_selector_disabled_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index 02b25f0..91b0cb8 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png
index e920499..5f55cd5 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png
index bbf5928..ed75cb8 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
index 4918d33..fcd81de 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png
index b69529c..12b0a79 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png
new file mode 100644
index 0000000..36348a8
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_text_cursor_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png
index f3d06fe..3ffa251 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
index f0e7db8..0eb61f1 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png
index d7faacf..0c766f3 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png
index 0a36039..4f66d7a 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png
new file mode 100644
index 0000000..530ca45
--- /dev/null
+++ b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png
new file mode 100644
index 0000000..b527495
--- /dev/null
+++ b/v7/appcompat/res/drawable-tvdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index 55099d4..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index 3c4701f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..6284eaa
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
index 9244174..4902520 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
index 5f40d73..59a683a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
index d068dbe..03bf49c 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
index 9924496..342323b 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..c0333f9
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..2f29c39
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 8a648b8..0d227438 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 435ce21..17fc083 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png
index ed8d341..600178a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 27bdcb7..c465e82 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
index 84968ee..76e07f0 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
index c10a1b7..1015e1f 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
index bd80981..b3fa6bc 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index a9e6cc560..c8a6d25 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
index ce5d4a7..3c5e683 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
index bb9d84d..f87733a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
index 9f9cb3b..9aabc43 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
index 53d0814..c039c8e 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
index 7accf52..b57ee19 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
index 05cfab7..76f2696 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
index b7d8dc7..d0385ba 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png
index 0d2836d..1e571f5 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_divider_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png
index b545f8e..67c25ae 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_focused_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png
index eda10e6..17c34a1 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_longpressed_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
index 29037a0..988548a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
index f4af926..15fcf6a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png
index 88726b6..65275b3 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png
index c6a7d4d..5b58e76 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_selector_disabled_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index 4fda867..c67a5dd 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png
index a081ceb..b5dd854 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png
index d4bd169..bcf6b7f 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
index fd47f15..cd1396b 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png
index 5610d8c..2242d2f 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png
new file mode 100644
index 0000000..666b10a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_text_cursor_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png
index 7174b67..8ff3a83 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png
index 46dad22..e7e693a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
index 33c10356..819171a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png
index 0226f84..4def8c8 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png
deleted file mode 100644
index d8cdf1a..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png
deleted file mode 100644
index a49a207..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png
new file mode 100644
index 0000000..4eae28f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ab_share_pack_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
index 0d544d9..accf80e 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
index 810a029..8c82ec3 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
index c9af24b..8fc0a9b 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
index db1d93a..92b712e 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
new file mode 100644
index 0000000..78bbeba
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
new file mode 100644
index 0000000..c4ba8e6
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index b149e47..133de52 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 00fb83ec..ff3b596 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png
index 1dd64b9..f6d2f32 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index c2d6a54..39178bf 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
index 24a194f..f54f4f9 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
index fc1b8b4..65cf0c1 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
index 8e1ab5b..d041623 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 5fc17a4..9dff893e 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
index 11a9f97..a1f8c33 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
index cada2fb..28a3bbf 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
index 556c30df..29a4e52 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
index f0a0b73..162ab9847 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
index 66f7d16..a1866ba 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
index 6f60bd3..d967ae7 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
index 658c5a5..5baef9f 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png
index b8ac46d..987b2bc 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
index 76cad17..8b050e8 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
index 8f436ea..00e370a 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
index d4952ea..719c7b5 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
index 1352a17..75bd580 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
index 175b82c..9cc3666 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png
index aad8a46..224a081 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index f5c18d0..2ab970d 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
index fb7d715..ee4bfe7 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 2e7bc12..6940b60 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
index 3e3174d..96bec46 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png
index 248f4f8..eeb74c8 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png
new file mode 100644
index 0000000..08ee2b47
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_text_cursor_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png
index 661d5f0..4d3d3a4 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png
index d7696c3..c5acb84 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
index b6efff3..30328ae 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
index 2b253fb..d4f3650 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
index 5dd0e5b..4dc870e 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
index f0ff1a7..4e18de2 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
index adef871..5fa3266 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
index 44028af..c11cb2e 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index d3f2a9a..e075ab8 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index a3caefb..25274ee 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
index 70c2040..16b0f1d 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
index 7252208..7b2a480 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
index 2a6f6ba82..fe93d87 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
index 13cc0fd..4b2d05a 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
index e232cf7..16e9e14 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
index 8e9041f..129d30f 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
index 66fc42f..fa6ab02 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..77318c7
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
index c873e9b..098c25a 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
index fe00ae5..76c4eeb 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 1086e9d..6b8bc0a 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
index 1e4a74c..c2393ab 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png
index 5813179..929be19 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_btn_borderless_material.xml b/v7/appcompat/res/drawable/abc_btn_borderless_material.xml
new file mode 100644
index 0000000..f389460
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_borderless_material.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/abc_btn_default_mtrl_shape"/>
+    <item android:state_pressed="true" android:drawable="@drawable/abc_btn_default_mtrl_shape"/>
+    <item android:drawable="@android:color/transparent"/>
+</selector>
+
diff --git a/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml b/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml
new file mode 100644
index 0000000..c50d4b1
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_default_mtrl_shape.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Used as the canonical button shape. -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="@dimen/abc_button_inset_horizontal_material"
+       android:insetTop="@dimen/abc_button_inset_vertical_material"
+       android:insetRight="@dimen/abc_button_inset_horizontal_material"
+       android:insetBottom="@dimen/abc_button_inset_vertical_material">
+    <shape android:shape="rectangle">
+        <corners android:radius="@dimen/abc_control_corner_material" />
+        <solid android:color="@android:color/white" />
+        <padding android:left="@dimen/abc_button_padding_horizontal_material"
+                 android:top="@dimen/abc_button_padding_vertical_material"
+                 android:right="@dimen/abc_button_padding_horizontal_material"
+                 android:bottom="@dimen/abc_button_padding_vertical_material" />
+    </shape>
+</inset>
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml b/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
new file mode 100644
index 0000000..41c4a6f
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="16dp"
+       android:insetTop="16dp"
+       android:insetRight="16dp"
+       android:insetBottom="16dp">
+    <shape android:shape="rectangle">
+        <corners android:radius="2dp" />
+        <solid android:color="@color/background_floating_material_dark" />
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml b/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
new file mode 100644
index 0000000..248b13a
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="16dp"
+       android:insetTop="16dp"
+       android:insetRight="16dp"
+       android:insetBottom="16dp">
+    <shape android:shape="rectangle">
+        <corners android:radius="2dp" />
+        <solid android:color="@color/background_floating_material_light" />
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_edit_text_material.xml b/v7/appcompat/res/drawable/abc_edit_text_material.xml
index 754ab18..46c4e91 100644
--- a/v7/appcompat/res/drawable/abc_edit_text_material.xml
+++ b/v7/appcompat/res/drawable/abc_edit_text_material.xml
@@ -15,16 +15,15 @@
 -->
 
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:insetLeft="@dimen/abc_control_inset_material"
-       android:insetTop="@dimen/abc_control_inset_material"
-       android:insetBottom="@dimen/abc_control_inset_material"
-       android:insetRight="@dimen/abc_control_inset_material">
+       android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
+       android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
+       android:insetTop="@dimen/abc_edit_text_inset_top_material"
+       android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
 
     <selector>
-        <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
-        <item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
-        <item android:state_enabled="true" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
-        <item android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
+        <item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
+        <item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
+        <item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
     </selector>
 
 </inset>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
new file mode 100644
index 0000000..535e2da2
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background"
+        android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
+    <item android:id="@android:id/secondaryProgress"
+        android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
+    <item android:id="@android:id/progress"
+        android:drawable="@drawable/abc_btn_rating_star_on_mtrl_alpha" />
+</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.xml b/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.xml
new file mode 100644
index 0000000..d0f46a8
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_spinner_textfield_background_material.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.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="@dimen/abc_control_inset_material"
+       android:insetTop="@dimen/abc_control_inset_material"
+       android:insetBottom="@dimen/abc_control_inset_material"
+       android:insetRight="@dimen/abc_control_inset_material">
+    <selector>
+        <item android:state_checked="false" android:state_pressed="false">
+            <layer-list>
+                <item android:drawable="@drawable/abc_textfield_default_mtrl_alpha" />
+                <item android:drawable="@drawable/abc_spinner_mtrl_am_alpha" />
+            </layer-list>
+        </item>
+        <item>
+            <layer-list>
+                <item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha" />
+                <item android:drawable="@drawable/abc_spinner_mtrl_am_alpha" />
+            </layer-list>
+        </item>
+    </selector>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view.xml b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
index 99c2395..923fda3 100644
--- a/v7/appcompat/res/layout/abc_activity_chooser_view.xml
+++ b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
@@ -16,14 +16,56 @@
 ** limitations under the License.
 */
 -->
-<android.support.v7.widget.LinearLayoutCompat
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+    class="android.support.v7.internal.widget.ActivityChooserView$InnerLayout"
     android:id="@+id/activity_chooser_view_content"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:layout_gravity="center"
     style="?attr/activityChooserViewStyle">
 
-    <include layout="@layout/abc_activity_chooser_view_include" />
+    <FrameLayout
+        android:id="@+id/expand_activities_button"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:focusable="true"
+        android:addStatesFromChildren="true"
+        android:background="?attr/actionBarItemBackground">
 
-</android.support.v7.widget.LinearLayoutCompat>
\ No newline at end of file
+        <ImageView android:id="@+id/image"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_gravity="center"
+            android:layout_marginTop="2dip"
+            android:layout_marginBottom="2dip"
+            android:layout_marginLeft="12dip"
+            android:layout_marginRight="12dip"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true" />
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/default_activity_button"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:focusable="true"
+        android:addStatesFromChildren="true"
+        android:background="?attr/actionBarItemBackground">
+
+        <ImageView android:id="@+id/image"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_gravity="center"
+            android:layout_marginTop="2dip"
+            android:layout_marginLeft="12dip"
+            android:layout_marginRight="12dip"
+            android:layout_marginEnd="12dip"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true" />
+
+    </FrameLayout>
+
+</view>
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml b/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml
deleted file mode 100644
index 975b13e..0000000
--- a/v7/appcompat/res/layout/abc_activity_chooser_view_include.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<merge
-    xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <FrameLayout
-        android:id="@+id/expand_activities_button"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:focusable="true"
-        android:addStatesFromChildren="true"
-        android:background="?attr/actionBarItemBackground">
-
-        <ImageView
-            android:id="@+id/image"
-            android:layout_width="56dip"
-            android:layout_height="36dip"
-            android:layout_gravity="center"
-            android:paddingTop="2dip"
-            android:paddingBottom="2dip"
-            android:paddingLeft="12dip"
-            android:paddingRight="12dip"
-            android:scaleType="fitCenter"
-            android:adjustViewBounds="true" />
-
-    </FrameLayout>
-
-    <FrameLayout
-        android:id="@+id/default_activity_button"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_gravity="center"
-        android:focusable="true"
-        android:addStatesFromChildren="true"
-        android:background="?attr/actionBarItemBackground">
-
-        <ImageView
-            android:id="@+id/image"
-            android:layout_width="56dip"
-            android:layout_height="36dip"
-            android:layout_gravity="center"
-            android:paddingTop="2dip"
-            android:paddingBottom="2dip"
-            android:paddingLeft="12dip"
-            android:paddingRight="12dip"
-            android:scaleType="fitCenter"
-            android:adjustViewBounds="true" />
-
-    </FrameLayout>
-
-</merge>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_dialog_title_material.xml b/v7/appcompat/res/layout/abc_dialog_title_material.xml
new file mode 100644
index 0000000..068b9e9
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_dialog_title_material.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<!--
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<android.support.v7.internal.widget.FitWindowsLinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        android:fitsSystemWindows="true">
+
+    <TextView
+            android:id="@+id/title"
+            style="?android:attr/windowTitleStyle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:paddingLeft="?attr/dialogPreferredPadding"
+            android:paddingRight="?attr/dialogPreferredPadding"
+            android:paddingTop="@dimen/abc_dialog_padding_top_material"/>
+
+    <include
+            layout="@layout/abc_screen_content_include"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"/>
+
+</android.support.v7.internal.widget.FitWindowsLinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_expanded_menu_layout.xml b/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
index 20e8b19..371151f 100644
--- a/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
+++ b/v7/appcompat/res/layout/abc_expanded_menu_layout.xml
@@ -18,5 +18,4 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/expanded_menu"
         android:layout_width="?attr/panelMenuListWidth"
-        android:layout_height="wrap_content"
-        android:background="?attr/panelBackground"/>
+        android:layout_height="wrap_content" />
diff --git a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
index 5a29686..7407498 100644
--- a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
+++ b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
@@ -24,7 +24,8 @@
 
     <!-- Icons come first in the layout, since their placement doesn't depend on
          the placement of the text views. -->
-    <ImageView android:id="@android:id/icon1"
+    <android.support.v7.internal.widget.TintImageView
+               android:id="@android:id/icon1"
                android:layout_width="@dimen/abc_dropdownitem_icon_width"
                android:layout_height="48dip"
                android:scaleType="centerInside"
@@ -33,7 +34,8 @@
                android:visibility="invisible"
                style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Icon1" />
 
-    <ImageView android:id="@+id/edit_query"
+    <android.support.v7.internal.widget.TintImageView
+               android:id="@+id/edit_query"
                android:layout_width="48dip"
                android:layout_height="48dip"
                android:scaleType="centerInside"
@@ -43,7 +45,8 @@
                android:visibility="gone"
                style="@style/RtlOverlay.Widget.AppCompat.Search.DropDown.Query" />
 
-    <ImageView android:id="@id/android:icon2"
+    <android.support.v7.internal.widget.TintImageView
+               android:id="@id/android:icon2"
                android:layout_width="48dip"
                android:layout_height="48dip"
                android:scaleType="centerInside"
diff --git a/v7/appcompat/res/values-af/strings.xml b/v7/appcompat/res/values-af/strings.xml
index 474f3aa..549ab76 100644
--- a/v7/appcompat/res/values-af/strings.xml
+++ b/v7/appcompat/res/values-af/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeer tuis"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeer op"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Nog opsies"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Vou in"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Soek"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Soek …"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Soeknavraag"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Vee navraag uit"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Dien navraag in"</string>
diff --git a/v7/appcompat/res/values-am/strings.xml b/v7/appcompat/res/values-am/strings.xml
index dbd53d6..9bcea17 100644
--- a/v7/appcompat/res/values-am/strings.xml
+++ b/v7/appcompat/res/values-am/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ወደ መነሻ ይዳስሱ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ወደ ላይ ይዳስሱ"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ተጨማሪ አማራጮች"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ሰብስብ"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s፣ %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s፣ %2$s፣ %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ፍለጋ"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ፈልግ…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"የፍለጋ ጥያቄ"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"መጠይቅ አጽዳ"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"መጠይቅ ያስረክቡ"</string>
diff --git a/v7/appcompat/res/values-ar/strings.xml b/v7/appcompat/res/values-ar/strings.xml
index 84d6fba..4ed5f59 100644
--- a/v7/appcompat/res/values-ar/strings.xml
+++ b/v7/appcompat/res/values-ar/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"التنقل إلى الشاشة الرئيسية"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"التنقل إلى أعلى"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"خيارات إضافية"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"تصغير"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s، %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s، %2$s، %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"بحث"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"بحث…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"طلب البحث"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"محو طلب البحث"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"إرسال طلب البحث"</string>
diff --git a/v7/appcompat/res/values-bg/strings.xml b/v7/appcompat/res/values-bg/strings.xml
index 9d87ef7..74963a2 100644
--- a/v7/appcompat/res/values-bg/strings.xml
+++ b/v7/appcompat/res/values-bg/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Придвижване към „Начало“"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Придвижване нагоре"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Още опции"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Свиване"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"„%1$s“ – %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"„%1$s“, „%2$s“ – %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Търсене"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Търсете…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Заявка за търсене"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Изчистване на заявката"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Изпращане на заявката"</string>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index ee522c6..93a5997 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"হোম এ নেভিগেট করুন"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"উপরের দিকে নেভিগেট করুন"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"আরো বিকল্প"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"সঙ্কুচিত করুন"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"অনুসন্ধান করুন"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"অনুসন্ধান..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ক্যোয়ারী অনুসন্ধান করুন"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ক্যোয়ারী সাফ করুন"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ক্যোয়ারী জমা দিন"</string>
diff --git a/v7/appcompat/res/values-ca/strings.xml b/v7/appcompat/res/values-ca/strings.xml
index 5fe4b0d..97789f5 100644
--- a/v7/appcompat/res/values-ca/strings.xml
+++ b/v7/appcompat/res/values-ca/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navega a la pàgina d\'inici"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navega cap a dalt"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Més opcions"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Replega"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Cerca..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de cerca"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Esborra la consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envia la consulta"</string>
diff --git a/v7/appcompat/res/values-cs/strings.xml b/v7/appcompat/res/values-cs/strings.xml
index 13c9ba8..9c3b2b0 100644
--- a/v7/appcompat/res/values-cs/strings.xml
+++ b/v7/appcompat/res/values-cs/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Přejít na plochu"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Přejít nahoru"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Více možností"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sbalit"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s – %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s – %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Hledat"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Vyhledat…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Vyhledávací dotaz"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Smazat dotaz"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Odeslat dotaz"</string>
diff --git a/v7/appcompat/res/values-da/strings.xml b/v7/appcompat/res/values-da/strings.xml
index 03fec32..fda0c24 100644
--- a/v7/appcompat/res/values-da/strings.xml
+++ b/v7/appcompat/res/values-da/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Naviger hjem"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Naviger op"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere muligheder"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skjul"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Søg"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Søg…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Søgeforespørgsel"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ryd forespørgslen"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Indsend forespørgslen"</string>
diff --git a/v7/appcompat/res/values-de/strings.xml b/v7/appcompat/res/values-de/strings.xml
index 8a0224c..2905d60 100644
--- a/v7/appcompat/res/values-de/strings.xml
+++ b/v7/appcompat/res/values-de/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zur Startseite"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nach oben"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Weitere Optionen"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Minimieren"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s: %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s: %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Suchen"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Suchen…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Suchanfrage"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Suchanfrage löschen"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Suchanfrage senden"</string>
diff --git a/v7/appcompat/res/values-el/strings.xml b/v7/appcompat/res/values-el/strings.xml
index 52d1b81..779c83f 100644
--- a/v7/appcompat/res/values-el/strings.xml
+++ b/v7/appcompat/res/values-el/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Πλοήγηση στην αρχική σελίδα"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Πλοήγηση προς τα επάνω"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Περισσότερες επιλογές"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Σύμπτυξη"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Αναζήτηση"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Αναζήτηση…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Ερώτημα αναζήτησης"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Διαγραφή ερωτήματος"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Υποβολή ερωτήματος"</string>
diff --git a/v7/appcompat/res/values-en-rGB/strings.xml b/v7/appcompat/res/values-en-rGB/strings.xml
index 8a8a111..a85156e 100644
--- a/v7/appcompat/res/values-en-rGB/strings.xml
+++ b/v7/appcompat/res/values-en-rGB/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Collapse"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Search…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Search query"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Clear query"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Submit query"</string>
diff --git a/v7/appcompat/res/values-en-rIN/strings.xml b/v7/appcompat/res/values-en-rIN/strings.xml
index 8a8a111..a85156e 100644
--- a/v7/appcompat/res/values-en-rIN/strings.xml
+++ b/v7/appcompat/res/values-en-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Collapse"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Search…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Search query"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Clear query"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Submit query"</string>
diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml
index ea5004c..b8488e1 100644
--- a/v7/appcompat/res/values-es-rUS/strings.xml
+++ b/v7/appcompat/res/values-es-rUS/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar a la página principal"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar hacia arriba"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Búsqueda"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Buscar…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de búsqueda"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Eliminar la consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
diff --git a/v7/appcompat/res/values-es/strings.xml b/v7/appcompat/res/values-es/strings.xml
index c50796e..70ea32d 100644
--- a/v7/appcompat/res/values-es/strings.xml
+++ b/v7/appcompat/res/values-es/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir a la pantalla de inicio"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desplazarse hacia arriba"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Buscar…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Borrar consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
diff --git a/v7/appcompat/res/values-et-rEE/strings.xml b/v7/appcompat/res/values-et-rEE/strings.xml
index 139fcf9..cf4deac 100644
--- a/v7/appcompat/res/values-et-rEE/strings.xml
+++ b/v7/appcompat/res/values-et-rEE/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeerimine avaekraanile"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeerimine üles"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Rohkem valikuid"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Ahendamine"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Otsing"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Otsige …"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Otsingupäring"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Päringu tühistamine"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Päringu esitamine"</string>
diff --git a/v7/appcompat/res/values-eu-rES/strings.xml b/v7/appcompat/res/values-eu-rES/strings.xml
index 541c2ed..dddc924 100644
--- a/v7/appcompat/res/values-eu-rES/strings.xml
+++ b/v7/appcompat/res/values-eu-rES/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Joan orri nagusira"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Joan gora"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Aukera gehiago"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Tolestu"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Bilatu"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Bilatu…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Bilaketa-kontsulta"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Garbitu kontsulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Bidali kontsulta"</string>
diff --git a/v7/appcompat/res/values-fa/strings.xml b/v7/appcompat/res/values-fa/strings.xml
index c317bda..3e85c47 100644
--- a/v7/appcompat/res/values-fa/strings.xml
+++ b/v7/appcompat/res/values-fa/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"پیمایش به صفحه اصلی"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"پیمایش به بالا"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"گزینه‌های بیشتر"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"کوچک کردن"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"‏%1$s‏، %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"‏%1$s‏، %2$s‏، %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"جستجو"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"جستجو…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"عبارت جستجو"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"پاک کردن عبارت جستجو"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ارسال عبارت جستجو"</string>
diff --git a/v7/appcompat/res/values-fi/strings.xml b/v7/appcompat/res/values-fi/strings.xml
index 218229b..f706ebe 100644
--- a/v7/appcompat/res/values-fi/strings.xml
+++ b/v7/appcompat/res/values-fi/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Siirry etusivulle"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Siirry ylös"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lisää"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Kutista"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Haku"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Haku…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Hakulauseke"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Tyhjennä kysely"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Lähetä kysely"</string>
diff --git a/v7/appcompat/res/values-fr-rCA/strings.xml b/v7/appcompat/res/values-fr-rCA/strings.xml
index 571ff9a..979bfa5 100644
--- a/v7/appcompat/res/values-fr-rCA/strings.xml
+++ b/v7/appcompat/res/values-fr-rCA/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Réduire"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Recherche en cours..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Requête de recherche"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Effacer la requête"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envoyer la requête"</string>
diff --git a/v7/appcompat/res/values-fr/strings.xml b/v7/appcompat/res/values-fr/strings.xml
index 353665a..df851d3 100644
--- a/v7/appcompat/res/values-fr/strings.xml
+++ b/v7/appcompat/res/values-fr/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Réduire"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Rechercher…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Requête de recherche"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Effacer la requête"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envoyer la requête"</string>
diff --git a/v7/appcompat/res/values-gl-rES/strings.xml b/v7/appcompat/res/values-gl-rES/strings.xml
index 3f665ed..618aec0 100644
--- a/v7/appcompat/res/values-gl-rES/strings.xml
+++ b/v7/appcompat/res/values-gl-rES/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir á páxina de inicio"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desprazarse cara arriba"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Máis opcións"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Contraer"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Buscar…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de busca"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Borrar consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
diff --git a/v7/appcompat/res/values-h720dp/dimens.xml b/v7/appcompat/res/values-h720dp/dimens.xml
new file mode 100644
index 0000000..09c43f0
--- /dev/null
+++ b/v7/appcompat/res/values-h720dp/dimens.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.
+-->
+
+<resources>
+    <!-- Dialog button bar height -->
+    <dimen name="abc_alert_dialog_button_bar_height">54dip</dimen>
+</resources>
diff --git a/v7/appcompat/res/values-hdpi/styles_base.xml b/v7/appcompat/res/values-hdpi/styles_base.xml
new file mode 100644
index 0000000..442ea29
--- /dev/null
+++ b/v7/appcompat/res/values-hdpi/styles_base.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+     <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="Base.Widget.AppCompat.DrawerArrowToggle.Common">
+          <item name="barSize">18.66dp</item>
+          <item name="gapBetweenBars">3.33dp</item>
+          <item name="drawableSize">24dp</item>
+     </style>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-hi/strings.xml b/v7/appcompat/res/values-hi/strings.xml
index d906afa..2ee69d96 100644
--- a/v7/appcompat/res/values-hi/strings.xml
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुख्यपृष्ठ पर नेविगेट करें"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ऊपर नेविगेट करें"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक विकल्प"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त करें"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"खोजें"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"खोजा जा रहा है…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"खोज क्वेरी"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"क्‍वेरी साफ़ करें"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"क्वेरी सबमिट करें"</string>
diff --git a/v7/appcompat/res/values-hr/strings.xml b/v7/appcompat/res/values-hr/strings.xml
index 0348596..7e8968f 100644
--- a/v7/appcompat/res/values-hr/strings.xml
+++ b/v7/appcompat/res/values-hr/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Idi na početnu"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Idi gore"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Dodatne opcije"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sažmi"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pretraživanje"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Pretražite…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Upit za pretraživanje"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Izbriši upit"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pošalji upit"</string>
diff --git a/v7/appcompat/res/values-hu/strings.xml b/v7/appcompat/res/values-hu/strings.xml
index fc67f00..7fe27d2 100644
--- a/v7/appcompat/res/values-hu/strings.xml
+++ b/v7/appcompat/res/values-hu/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ugrás a főoldalra"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Felfelé mozgatás"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"További lehetőségek"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Összecsukás"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Keresés"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Keresés…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Keresési lekérdezés"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Lekérdezés törlése"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Lekérdezés küldése"</string>
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml
index da67fe4..47c29a5 100644
--- a/v7/appcompat/res/values-hy-rAM/strings.xml
+++ b/v7/appcompat/res/values-hy-rAM/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ուղղվել տուն"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Ուղղվել վերև"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Այլ ընտրանքներ"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Թաքցնել"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Որոնել"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Որոնում..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Որոնման հարցում"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Մաքրել հարցումը"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Ուղարկել հարցումը"</string>
diff --git a/v7/appcompat/res/values-in/strings.xml b/v7/appcompat/res/values-in/strings.xml
index 3c31755..d102ba6 100644
--- a/v7/appcompat/res/values-in/strings.xml
+++ b/v7/appcompat/res/values-in/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi ke beranda"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi naik"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Opsi lain"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Ciutkan"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Telusuri"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Telusuri..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Kueri penelusuran"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Hapus kueri"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Kirim kueri"</string>
diff --git a/v7/appcompat/res/values-is-rIS/strings.xml b/v7/appcompat/res/values-is-rIS/strings.xml
index 7846b51..a205e8b 100644
--- a/v7/appcompat/res/values-is-rIS/strings.xml
+++ b/v7/appcompat/res/values-is-rIS/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Fara heim"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Fara upp"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fleiri valkostir"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Minnka"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Leita"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Leita…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Leitarfyrirspurn"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Hreinsa fyrirspurn"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Senda fyrirspurn"</string>
diff --git a/v7/appcompat/res/values-it/strings.xml b/v7/appcompat/res/values-it/strings.xml
index 6ed52be..71cb54f 100644
--- a/v7/appcompat/res/values-it/strings.xml
+++ b/v7/appcompat/res/values-it/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Vai alla home page"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Vai in alto"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Altre opzioni"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Comprimi"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Cerca…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Query di ricerca"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Cancella query"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Invia query"</string>
diff --git a/v7/appcompat/res/values-iw/strings.xml b/v7/appcompat/res/values-iw/strings.xml
index fec0e62..c5ef730 100644
--- a/v7/appcompat/res/values-iw/strings.xml
+++ b/v7/appcompat/res/values-iw/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"נווט לדף הבית"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"נווט למעלה"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"עוד אפשרויות"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"כווץ"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"‏%1$s‏, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"‏%1$s‏, %2$s‏, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"חפש"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"חפש…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"שאילתת חיפוש"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"מחק שאילתה"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"שלח שאילתה"</string>
diff --git a/v7/appcompat/res/values-ja/strings.xml b/v7/appcompat/res/values-ja/strings.xml
index 181dd5e..736d454 100644
--- a/v7/appcompat/res/values-ja/strings.xml
+++ b/v7/appcompat/res/values-ja/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ホームへ移動"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"上へ移動"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"その他のオプション"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"折りたたむ"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s、%2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s、%2$s、%3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"検索"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"検索…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"検索キーワード"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"検索キーワードを削除"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"検索キーワードを送信"</string>
diff --git a/v7/appcompat/res/values-ka-rGE/strings.xml b/v7/appcompat/res/values-ka-rGE/strings.xml
index a96f26c..2341e3d 100644
--- a/v7/appcompat/res/values-ka-rGE/strings.xml
+++ b/v7/appcompat/res/values-ka-rGE/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"მთავარზე ნავიგაცია"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ზემოთ ნავიგაცია"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"მეტი ვარიანტები"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"აკეცვა"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ძიება"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ძიება..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ძიების მოთხოვნა"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"მოთხოვნის გასუფთავება"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"მოთხოვნის გადაგზავნა"</string>
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk-rKZ/strings.xml
index fb20a00..1af9a5ff 100644
--- a/v7/appcompat/res/values-kk-rKZ/strings.xml
+++ b/v7/appcompat/res/values-kk-rKZ/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Негізгі бетте қозғалу"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жоғары қозғалу"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Басқа опциялар"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Тасалау"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Іздеу"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Іздеу…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Сұрақты іздеу"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Сұрақты жою"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Сұрақты жіберу"</string>
diff --git a/v7/appcompat/res/values-km-rKH/strings.xml b/v7/appcompat/res/values-km-rKH/strings.xml
index 704f4ee..5fda61a 100644
--- a/v7/appcompat/res/values-km-rKH/strings.xml
+++ b/v7/appcompat/res/values-km-rKH/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"រកមើល​ទៅ​ដើម"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"រកមើល​ឡើងលើ"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ជម្រើស​ច្រើន​ទៀត"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"បង្រួម"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ស្វែងរក"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ស្វែងរក…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ស្វែងរក​សំណួរ"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"សម្អាត​សំណួរ"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ដាក់​​​ស្នើ​សំណួរ"</string>
diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn-rIN/strings.xml
index d472ff3..669c21c 100644
--- a/v7/appcompat/res/values-kn-rIN/strings.xml
+++ b/v7/appcompat/res/values-kn-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ಮುಖಪುಟವನ್ನು ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ಸಂಕುಚಿಸು"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ಹುಡುಕು"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ಹುಡುಕಿ…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು"</string>
diff --git a/v7/appcompat/res/values-ko/strings.xml b/v7/appcompat/res/values-ko/strings.xml
index 0a92a83..ad83225 100644
--- a/v7/appcompat/res/values-ko/strings.xml
+++ b/v7/appcompat/res/values-ko/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"홈 탐색"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"위로 탐색"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"옵션 더보기"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"접기"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"검색"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"검색..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"검색어"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"검색어 삭제"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"검색어 보내기"</string>
diff --git a/v7/appcompat/res/values-ky-rKG/strings.xml b/v7/appcompat/res/values-ky-rKG/strings.xml
index b091f60..b9f0bb1 100644
--- a/v7/appcompat/res/values-ky-rKG/strings.xml
+++ b/v7/appcompat/res/values-ky-rKG/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Үйгө багыттоо"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жогору"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Көбүрөөк мүмкүнчүлүктөр"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Жыйнап коюу"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Издөө"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Издөө…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Издөө талаптары"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Талаптарды тазалоо"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Талап жөнөтүү"</string>
diff --git a/v7/appcompat/res/values-lo-rLA/strings.xml b/v7/appcompat/res/values-lo-rLA/strings.xml
index 33614e6..45f830f 100644
--- a/v7/appcompat/res/values-lo-rLA/strings.xml
+++ b/v7/appcompat/res/values-lo-rLA/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ກັບໄປໜ້າຫຼັກ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ຂຶ້ນເທິງ"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ໂຕເລືອກອື່ນ"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ຫຍໍ້"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ຊອກຫາ"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ຊອກຫາ"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ຊອກຫາ"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ລຶບຂໍ້ຄວາມຊອກຫາ"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ສົ່ງການຊອກຫາ"</string>
diff --git a/v7/appcompat/res/values-lt/strings.xml b/v7/appcompat/res/values-lt/strings.xml
index 3c992a7..27713a7 100644
--- a/v7/appcompat/res/values-lt/strings.xml
+++ b/v7/appcompat/res/values-lt/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Eiti į pagrindinį puslapį"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Eiti į viršų"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Daugiau parinkčių"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sutraukti"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Paieška"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Ieškoti..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Paieškos užklausa"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Išvalyti užklausą"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pateikti užklausą"</string>
diff --git a/v7/appcompat/res/values-lv/strings.xml b/v7/appcompat/res/values-lv/strings.xml
index 3bd7259..986e8eb 100644
--- a/v7/appcompat/res/values-lv/strings.xml
+++ b/v7/appcompat/res/values-lv/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Pārvietoties uz sākuma ekrānu"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Pārvietoties augšup"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Vairāk opciju"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Sakļaut"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s: %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s: %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Meklēt"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Meklējiet…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Meklēšanas vaicājums"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Notīrīt vaicājumu"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Iesniegt vaicājumu"</string>
diff --git a/v7/appcompat/res/values-mk-rMK/strings.xml b/v7/appcompat/res/values-mk-rMK/strings.xml
index b1abf10..916809a 100644
--- a/v7/appcompat/res/values-mk-rMK/strings.xml
+++ b/v7/appcompat/res/values-mk-rMK/strings.xml
@@ -20,11 +20,13 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Движи се кон дома"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Движи се нагоре"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Повеќе опции"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Собери"</string>
     <!-- String.format failed for translation -->
     <!-- no translation found for abc_action_bar_home_description_format (1397052879051804371) -->
     <skip />
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Пребарај"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Пребарување…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Пребарај барање"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Исчисти барање"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Поднеси барање"</string>
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
index b1f0fb9..c122ed5 100644
--- a/v7/appcompat/res/values-ml-rIN/strings.xml
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ഹോമിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"മുകളിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"കൂടുതല്‍ ഓപ്‌ഷനുകള്‍"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ചുരുക്കുക"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"തിരയൽ"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"തിരയുക…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"തിരയൽ അന്വേഷണം"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"അന്വേഷണം മായ്‌ക്കുക"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"അന്വേഷണം സമർപ്പിക്കുക"</string>
diff --git a/v7/appcompat/res/values-mn-rMN/strings.xml b/v7/appcompat/res/values-mn-rMN/strings.xml
index a1a9c6f..eadbb9f 100644
--- a/v7/appcompat/res/values-mn-rMN/strings.xml
+++ b/v7/appcompat/res/values-mn-rMN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Нүүр хуудас руу шилжих"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Дээш шилжих"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Нэмэлт сонголтууд"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Хумих"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Хайх"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Хайх..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Хайх асуулга"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Асуулгыг цэвэрлэх"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Асуулгыг илгээх"</string>
diff --git a/v7/appcompat/res/values-mr-rIN/strings.xml b/v7/appcompat/res/values-mr-rIN/strings.xml
index 3d0e718..a2fb945 100644
--- a/v7/appcompat/res/values-mr-rIN/strings.xml
+++ b/v7/appcompat/res/values-mr-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुख्‍यपृष्‍ठ नेव्‍हिगेट करा"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"वर नेव्‍हिगेट करा"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक पर्याय"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त करा"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"शोध"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"शोधा…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"शोध क्वेरी"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"क्‍वेरी स्‍पष्‍ट करा"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"क्वेरी सबमिट करा"</string>
diff --git a/v7/appcompat/res/values-ms-rMY/strings.xml b/v7/appcompat/res/values-ms-rMY/strings.xml
index d2886a1..f42fe3d 100644
--- a/v7/appcompat/res/values-ms-rMY/strings.xml
+++ b/v7/appcompat/res/values-ms-rMY/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi skrin utama"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi ke atas"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lagi pilihan"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Runtuhkan"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cari"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Cari…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Pertanyaan carian"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Kosongkan pertanyaan"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Serah pertanyaan"</string>
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my-rMM/strings.xml
index 3ac8472..6313bcb 100644
--- a/v7/appcompat/res/values-my-rMM/strings.xml
+++ b/v7/appcompat/res/values-my-rMM/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"မူလနေရာကို သွားရန်"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"အပေါ်သို့သွားရန်"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ပိုမိုရွေးချယ်စရာများ"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ခေါက်ရန်"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s၊ %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s ၊ %2$s ၊ %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ရှာဖွေရန်"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ရှာဖွေပါ..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ရှာစရာ အချက်အလက်နေရာ"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ရှာစရာ အချက်အလက်များ ရှင်းလင်းရန်"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ရှာဖွေစရာ အချက်အလက်ကို အတည်ပြုရန်"</string>
diff --git a/v7/appcompat/res/values-nb/strings.xml b/v7/appcompat/res/values-nb/strings.xml
index 3dbd071..6e50a58 100644
--- a/v7/appcompat/res/values-nb/strings.xml
+++ b/v7/appcompat/res/values-nb/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Gå til startsiden"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Gå opp"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere alternativer"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skjul"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s – %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s – %2$s – %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Søk"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Søk …"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Søkeord"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Slett søket"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Utfør søket"</string>
diff --git a/v7/appcompat/res/values-ne-rNP/strings.xml b/v7/appcompat/res/values-ne-rNP/strings.xml
index 0154662..5b804d8 100644
--- a/v7/appcompat/res/values-ne-rNP/strings.xml
+++ b/v7/appcompat/res/values-ne-rNP/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"गृह खोज्नुहोस्"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"माथि खोज्नुहोस्"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"थप विकल्पहरू"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"संक्षिप्त पार्नुहोस्"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"खोज्नुहोस्"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"खोज्नुहोस्..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"जिज्ञासाको खोज गर्नुहोस्"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"प्रश्‍न हटाउनुहोस्"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"जिज्ञासा पेस गर्नुहोस्"</string>
diff --git a/v7/appcompat/res/values-nl/strings.xml b/v7/appcompat/res/values-nl/strings.xml
index 330de8d..61546df 100644
--- a/v7/appcompat/res/values-nl/strings.xml
+++ b/v7/appcompat/res/values-nl/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeren naar startpositie"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Omhoog navigeren"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Meer opties"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Samenvouwen"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Zoeken"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Zoeken…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Zoekopdracht"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Zoekopdracht wissen"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Zoekopdracht verzenden"</string>
diff --git a/v7/appcompat/res/values-pl/strings.xml b/v7/appcompat/res/values-pl/strings.xml
index 8e32155..9d99e92 100644
--- a/v7/appcompat/res/values-pl/strings.xml
+++ b/v7/appcompat/res/values-pl/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Przejdź do strony głównej"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Przejdź wyżej"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Więcej opcji"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Zwiń"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Szukaj"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Szukaj…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Wyszukiwane hasło"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Wyczyść zapytanie"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Wyślij zapytanie"</string>
diff --git a/v7/appcompat/res/values-pt-rPT/strings.xml b/v7/appcompat/res/values-pt-rPT/strings.xml
index e1c622e..e905530 100644
--- a/v7/appcompat/res/values-pt-rPT/strings.xml
+++ b/v7/appcompat/res/values-pt-rPT/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Reduzir"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Pesquisar..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de pesquisa"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Limpar consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
diff --git a/v7/appcompat/res/values-pt/strings.xml b/v7/appcompat/res/values-pt/strings.xml
index abdd650..b6c94e90 100644
--- a/v7/appcompat/res/values-pt/strings.xml
+++ b/v7/appcompat/res/values-pt/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Recolher"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Pesquisar..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de pesquisa"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Limpar consulta"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
diff --git a/v7/appcompat/res/values-ro/strings.xml b/v7/appcompat/res/values-ro/strings.xml
index 6dd2b67..4c741f39 100644
--- a/v7/appcompat/res/values-ro/strings.xml
+++ b/v7/appcompat/res/values-ro/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigați la ecranul de pornire"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigați în sus"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mai multe opțiuni"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Restrângeți"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Căutați"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Căutați…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Interogare de căutare"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ștergeți interogarea"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Trimiteți interogarea"</string>
diff --git a/v7/appcompat/res/values-ru/strings.xml b/v7/appcompat/res/values-ru/strings.xml
index 9c5ed89..4879b5d 100644
--- a/v7/appcompat/res/values-ru/strings.xml
+++ b/v7/appcompat/res/values-ru/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на главный экран"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вверх"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Другие параметры"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Свернуть"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Поиск"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Поиск"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Поисковый запрос"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Удалить запрос"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Отправить запрос"</string>
diff --git a/v7/appcompat/res/values-si-rLK/strings.xml b/v7/appcompat/res/values-si-rLK/strings.xml
index 22809d6..252448f 100644
--- a/v7/appcompat/res/values-si-rLK/strings.xml
+++ b/v7/appcompat/res/values-si-rLK/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ගෙදරට සංචාලනය කරන්න"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ඉහලට සංචාලනය කරන්න"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"තවත් විකල්ප"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"හකුළන්න"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"සෙවීම"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"සොයන්න..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"සෙවුම් විමසුම"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"විමසුම හිස් කරන්න"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"විමසුම යොමු කරන්න"</string>
diff --git a/v7/appcompat/res/values-sk/strings.xml b/v7/appcompat/res/values-sk/strings.xml
index 2b09cce..501e0653 100644
--- a/v7/appcompat/res/values-sk/strings.xml
+++ b/v7/appcompat/res/values-sk/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Prejsť na plochu"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Prejsť hore"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Ďalšie možnosti"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Zbaliť"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Hľadať"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Vyhľadať…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Vyhľadávací dopyt"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Vymazať dopyt"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Odoslať dopyt"</string>
diff --git a/v7/appcompat/res/values-sl/strings.xml b/v7/appcompat/res/values-sl/strings.xml
index a522de2..da49123 100644
--- a/v7/appcompat/res/values-sl/strings.xml
+++ b/v7/appcompat/res/values-sl/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Krmarjenje domov"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Krmarjenje navzgor"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Več možnosti"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Strni"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Iskanje"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Iskanje …"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Iskalna poizvedba"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Izbris poizvedbe"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pošiljanje poizvedbe"</string>
diff --git a/v7/appcompat/res/values-sr/strings.xml b/v7/appcompat/res/values-sr/strings.xml
index c26945b..9e2a9e8 100644
--- a/v7/appcompat/res/values-sr/strings.xml
+++ b/v7/appcompat/res/values-sr/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Одлазак на Почетну"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Кретање нагоре"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Још опција"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Скупи"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Претрага"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Претражите..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Упит за претрагу"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Брисање упита"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Слање упита"</string>
diff --git a/v7/appcompat/res/values-sv/strings.xml b/v7/appcompat/res/values-sv/strings.xml
index 3120ad80..905e3ea 100644
--- a/v7/appcompat/res/values-sv/strings.xml
+++ b/v7/appcompat/res/values-sv/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Visa startsidan"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigera uppåt"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fler alternativ"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Komprimera"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Sök"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Sök …"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Sökfråga"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ta bort frågan"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Skicka fråga"</string>
diff --git a/v7/appcompat/res/values-sw/strings.xml b/v7/appcompat/res/values-sw/strings.xml
index afe54f6..7287c0d 100644
--- a/v7/appcompat/res/values-sw/strings.xml
+++ b/v7/appcompat/res/values-sw/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Nenda mwanzo"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nenda juu"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Chaguo zaidi"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Kunja"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tafuta"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Tafuta…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Hoja ya utafutaji"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Futa hoja"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Wasilisha hoja"</string>
diff --git a/v7/appcompat/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-sw600dp/dimens.xml
index cba2150..e221b50 100644
--- a/v7/appcompat/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-sw600dp/dimens.xml
@@ -29,5 +29,12 @@
     <dimen name="abc_action_bar_default_height_material">64dp</dimen>
     <!-- Default padding of an action bar. -->
     <dimen name="abc_action_bar_default_padding_material">4dp</dimen>
+    <!-- Default content inset of an action bar. -->
+    <dimen name="abc_action_bar_content_inset_material">24dp</dimen>
+
+    <!-- Padding to add to the start of the overflow action button. -->
+    <dimen name="abc_action_bar_navigation_padding_start_material">8dp</dimen>
+    <!-- Padding to add to the end of the overflow action button. -->
+    <dimen name="abc_action_bar_overflow_padding_end_material">18dp</dimen>
 
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-ta-rIN/strings.xml b/v7/appcompat/res/values-ta-rIN/strings.xml
index 542fd34..ab728a6 100644
--- a/v7/appcompat/res/values-ta-rIN/strings.xml
+++ b/v7/appcompat/res/values-ta-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"முகப்பிற்கு வழிசெலுத்து"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"மேலே வழிசெலுத்து"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"மேலும் விருப்பங்கள்"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"சுருக்கு"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"தேடு"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"தேடு..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"தேடல் வினவல்"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"வினவலை அழி"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"வினவலைச் சமர்ப்பி"</string>
diff --git a/v7/appcompat/res/values-te-rIN/strings.xml b/v7/appcompat/res/values-te-rIN/strings.xml
index 5f36cc5..901859b0 100644
--- a/v7/appcompat/res/values-te-rIN/strings.xml
+++ b/v7/appcompat/res/values-te-rIN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"హోమ్‌కు నావిగేట్ చేయండి"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"పైకి నావిగేట్ చేయండి"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"మరిన్ని ఎంపికలు"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"కుదించండి"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"శోధించు"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"శోధించు..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ప్రశ్న శోధించండి"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ప్రశ్నను క్లియర్ చేయి"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ప్రశ్నని సమర్పించు"</string>
diff --git a/v7/appcompat/res/values-th/strings.xml b/v7/appcompat/res/values-th/strings.xml
index d8c04c4..e962aa5 100644
--- a/v7/appcompat/res/values-th/strings.xml
+++ b/v7/appcompat/res/values-th/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"นำทางไปหน้าแรก"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"นำทางขึ้น"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ตัวเลือกอื่น"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ยุบ"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"ค้นหา"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"ค้นหา…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"ข้อความค้นหา"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ล้างข้อความค้นหา"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ส่งข้อความค้นหา"</string>
diff --git a/v7/appcompat/res/values-tl/strings.xml b/v7/appcompat/res/values-tl/strings.xml
index 0384435..f41b15f 100644
--- a/v7/appcompat/res/values-tl/strings.xml
+++ b/v7/appcompat/res/values-tl/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Mag-navigate patungo sa home"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Mag-navigate pataas"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Higit pang mga opsyon"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"I-collapse"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Maghanap"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Maghanap…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Query sa paghahanap"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"I-clear ang query"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Isumite ang query"</string>
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
index c06069c..9cde4a2 100644
--- a/v7/appcompat/res/values-tr/strings.xml
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ana ekrana git"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yukarı git"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Diğer seçenekler"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Daralt"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Ara"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Ara…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Arama sorgusu"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Sorguyu temizle"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Sorguyu gönder"</string>
diff --git a/v7/appcompat/res/values-uk/strings.xml b/v7/appcompat/res/values-uk/strings.xml
index d07404b..0a5c31cd 100644
--- a/v7/appcompat/res/values-uk/strings.xml
+++ b/v7/appcompat/res/values-uk/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на головний"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вгору"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Інші опції"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Згорнути"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Пошук"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Пошук…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Пошуковий запит"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Очистити запит"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Надіслати запит"</string>
diff --git a/v7/appcompat/res/values-ur-rPK/strings.xml b/v7/appcompat/res/values-ur-rPK/strings.xml
index 89c0ea6..e6f6260 100644
--- a/v7/appcompat/res/values-ur-rPK/strings.xml
+++ b/v7/appcompat/res/values-ur-rPK/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ہوم پر نیویگیٹ کریں"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"اوپر نیویگیٹ کریں"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"مزید اختیارات"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"سکیڑیں"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"تلاش کریں"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"تلاش کریں…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"استفسار تلاش کریں"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"استفسار صاف کریں"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"استفسار جمع کرائیں"</string>
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
index 537afa1..241b3b1 100644
--- a/v7/appcompat/res/values-uz-rUZ/strings.xml
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Boshiga o‘tish"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuqoriga o‘tish"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Qo‘shimcha sozlamalar"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Yig‘ish"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Izlash"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Qidirish…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"So‘rovni izlash"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"So‘rovni tozalash"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"So‘rov yaratish"</string>
diff --git a/v7/appcompat/res/values-v11/styles_base_text.xml b/v7/appcompat/res/values-v11/styles_base_text.xml
index d7118c0..4cf4966 100644
--- a/v7/appcompat/res/values-v11/styles_base_text.xml
+++ b/v7/appcompat/res/values-v11/styles_base_text.xml
@@ -17,6 +17,20 @@
 
 <resources>
 
+    <style name="Base.TextAppearance.AppCompat.Title.Inverse">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Subhead.Inverse">
+        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
     <!-- Deprecated text styles -->
 
     <style name="Base.TextAppearance.AppCompat.Inverse">
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index ca583fa..d344bf5 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -22,12 +22,17 @@
         unbundled Action Bar.
     -->
     <eat-comment/>
-    <style name="Platform.AppCompat" parent="android:Theme.Holo">
+
+    <style name="Platform.AppCompat" parent="Platform.V11.AppCompat" />
+    <style name="Platform.AppCompat.Light" parent="Platform.V11.AppCompat.Light" />
+
+    <style name="Platform.V11.AppCompat" parent="android:Theme.Holo">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
+        <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
+        <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
+
         <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
 
         <!-- Window colors -->
@@ -35,7 +40,7 @@
         <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
         <item name="android:colorBackground">@color/background_material_dark</item>
         <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
-        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:disabledAlpha">@dimen/abc_disabled_alpha_material_dark</item>
         <item name="android:backgroundDimAmount">0.6</item>
         <item name="android:windowBackground">@color/background_material_dark</item>
 
@@ -66,18 +71,24 @@
         <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
 
         <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+        <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
         <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
 
         <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
         <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
+
+        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
+        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
+        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
     </style>
 
-    <style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">
+    <style name="Platform.V11.AppCompat.Light" parent="android:Theme.Holo.Light">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
+        <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
+        <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
+
         <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
 
         <!-- Window colors -->
@@ -85,7 +96,7 @@
         <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
         <item name="android:colorBackground">@color/background_material_light</item>
         <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
-        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:disabledAlpha">@dimen/abc_disabled_alpha_material_light</item>
         <item name="android:backgroundDimAmount">0.6</item>
         <item name="android:windowBackground">@color/background_material_light</item>
 
@@ -117,246 +128,30 @@
         <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
 
         <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+        <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
         <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
 
         <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
         <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
-    </style>
 
-    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Holo.Dialog">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowActionBar">false</item>
-
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
-        <!-- Window colors -->
-        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
-        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
-        <item name="android:colorBackground">@color/background_material_dark</item>
-        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
-        <item name="android:disabledAlpha">0.5</item>
-        <item name="android:backgroundDimAmount">0.6</item>
-        <item name="android:windowBackground">@color/background_material_dark</item>
-
-        <!-- Text colors -->
-        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
-        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
-        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
-        <item name="android:textColorSecondary">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorSecondaryInverse">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorTertiary">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
-        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
-        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
-        <item name="android:textColorHighlightInverse">@color/highlighted_text_material_light</item>
-        <item name="android:textColorLink">@color/link_text_material_dark</item>
-        <item name="android:textColorLinkInverse">@color/link_text_material_light</item>
-        <item name="android:textColorAlertDialogListItem">@color/abc_primary_text_material_dark</item>
-
-        <!-- Text styles -->
-        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
-        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
-        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
-        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
-        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
-        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
-        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
-        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
-
-        <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
-        <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
-        <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
-    </style>
-
-    <style name="Platform.AppCompat.Light.Dialog" parent="android:Theme.Holo.Light.Dialog">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowActionBar">false</item>
-
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
-        <!-- Window colors -->
-        <item name="android:colorForeground">@color/bright_foreground_material_light</item>
-        <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
-        <item name="android:colorBackground">@color/background_material_light</item>
-        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
-        <item name="android:disabledAlpha">0.5</item>
-        <item name="android:backgroundDimAmount">0.6</item>
-        <item name="android:windowBackground">@color/background_material_light</item>
-
-        <!-- Text colors -->
-        <item name="android:textColorPrimary">@color/abc_primary_text_material_light</item>
-        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_dark</item>
-        <item name="android:textColorSecondary">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorSecondaryInverse">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorTertiary">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_light</item>
-        <item name="android:textColorPrimaryInverseDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
-        <item name="android:textColorHint">@color/hint_foreground_material_light</item>
-        <item name="android:textColorHintInverse">@color/hint_foreground_material_dark</item>
-        <item name="android:textColorHighlight">@color/highlighted_text_material_light</item>
-        <item name="android:textColorHighlightInverse">@color/highlighted_text_material_dark</item>
-        <item name="android:textColorLink">@color/link_text_material_light</item>
-        <item name="android:textColorLinkInverse">@color/link_text_material_dark</item>
-        <item name="android:textColorAlertDialogListItem">@color/abc_primary_text_material_light</item>
-
-        <!-- Text styles -->
-        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
-        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
-        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
-        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
-        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
-        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
-        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
-        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
-
-        <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
-        <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
-        <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
-    </style>
-
-    <style name="Base.V11.Theme.AppCompat" parent="Base.V7.Theme.AppCompat" />
-    <style name="Base.V11.Theme.AppCompat.Light" parent="Base.V7.Theme.AppCompat.Light" />
-    <style name="Base.V11.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
-
-    <style name="Base.V11.Theme.AppCompat.Light.Dialog" parent="Platform.AppCompat.Light.Dialog">
-        <item name="windowActionBar">true</item>
-        <item name="windowActionBarOverlay">false</item>
-        <item name="isLightTheme">true</item>
-
-        <item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
-        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
-
-        <item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
-        <item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
-
-        <!-- Action Bar Styles -->
-        <item name="actionBarTabStyle">@style/Widget.AppCompat.Light.ActionBar.TabView</item>
-        <item name="actionBarTabBarStyle">@style/Widget.AppCompat.Light.ActionBar.TabBar</item>
-        <item name="actionBarTabTextStyle">@style/Widget.AppCompat.Light.ActionBar.TabText</item>
-        <item name="actionButtonStyle">@style/Widget.AppCompat.Light.ActionButton</item>
-        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.Light.ActionButton.Overflow</item>
-        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.Light.PopupMenu.Overflow</item>
-        <item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar.Solid</item>
-        <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
-        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
-        <item name="actionBarDivider">?attr/dividerVertical</item>
-        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
-        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
-        <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
-
-        <!-- Action Mode -->
-        <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
-        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
-        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
-
-        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
-        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
-        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
-        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
-
-        <!-- Dropdown Spinner Attributes -->
-        <item name="actionDropDownStyle">@style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar</item>
-
-        <!-- Panel attributes -->
-        <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
-        <item name="panelMenuListTheme">@style/Theme.AppCompat.CompactMenu</item>
-        <item name="android:panelBackground">@android:color/transparent</item>
-        <item name="panelBackground">@drawable/abc_menu_hardkey_panel_mtrl_mult</item>
-        <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_light</item>
-
-        <!-- List attributes -->
-        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
-        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
-        <item name="listPreferredItemHeight">64dp</item>
-        <item name="listPreferredItemHeightSmall">48dp</item>
-        <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">16dip</item>
-        <item name="listPreferredItemPaddingRight">16dip</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
-        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
-
-        <!-- Popup Menu styles -->
-        <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
-        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
-        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
-        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
-        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <!-- SearchView attributes -->
-        <item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
-        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
-        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
-        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.SearchResult.Title</item>
-        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
-
-        <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
-
-        <!-- Toolbar styles -->
-        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
-        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
-
-        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
-        <item name="editTextBackground">@drawable/abc_edit_text_material</item>
-        <item name="editTextColor">?android:attr/textColorPrimary</item>
-
-        <!-- Color palette -->
-        <item name="colorPrimaryDark">@color/primary_dark_material_light</item>
-        <item name="colorPrimary">@color/primary_material_light</item>
-        <item name="colorAccent">@color/accent_material_light</item>
-
-        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
-        <item name="colorControlActivated">?attr/colorAccent</item>
-        <item name="colorControlHighlight">@color/ripple_material_light</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
-
-        <item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
-    </style>
-
-    <style name="Base.Theme.AppCompat" parent="Base.V11.Theme.AppCompat">
         <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
         <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
         <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
     </style>
 
-    <style name="Base.Theme.AppCompat.Light" parent="Base.V11.Theme.AppCompat.Light">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
+    <style name="Base.V11.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog">
+        <item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
+        <item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
+        <item name="android:windowCloseOnTouchOutside">@bool/abc_config_closeDialogWhenTouchOutside</item>
     </style>
 
-    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
+    <style name="Base.V11.Theme.AppCompat.Light.Dialog" parent="Base.V7.Theme.AppCompat.Light.Dialog">
+        <item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
+        <item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
+        <item name="android:windowCloseOnTouchOutside">@bool/abc_config_closeDialogWhenTouchOutside</item>
     </style>
 
-    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
-    </style>
+    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog" />
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog" />
 
 </resources>
diff --git a/v7/appcompat/res/values-v12/themes_base.xml b/v7/appcompat/res/values-v12/themes_base.xml
new file mode 100644
index 0000000..c912434
--- /dev/null
+++ b/v7/appcompat/res/values-v12/themes_base.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <style name="Platform.AppCompat" parent="Platform.V12.AppCompat" />
+    <style name="Platform.AppCompat.Light" parent="Platform.V12.AppCompat.Light" />
+
+    <style name="Platform.V12.AppCompat" parent="Platform.V11.AppCompat">
+        <item name="android:textCursorDrawable">@drawable/abc_text_cursor_mtrl_alpha</item>
+    </style>
+
+    <style name="Platform.V12.AppCompat.Light" parent="Platform.V11.AppCompat.Light">
+        <item name="android:textCursorDrawable">@drawable/abc_text_cursor_mtrl_alpha</item>
+    </style>
+
+</resources>
diff --git a/v7/appcompat/res/values-v14/styles_base_text.xml b/v7/appcompat/res/values-v14/styles_base_text.xml
new file mode 100644
index 0000000..54c8de2
--- /dev/null
+++ b/v7/appcompat/res/values-v14/styles_base_text.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+
+    <style name="Base.TextAppearance.AppCompat.Button">
+        <item name="android:textSize">@dimen/abc_text_size_button_material</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v14/themes_base.xml b/v7/appcompat/res/values-v14/themes_base.xml
index 3f26ca2..5fdc73c 100644
--- a/v7/appcompat/res/values-v14/themes_base.xml
+++ b/v7/appcompat/res/values-v14/themes_base.xml
@@ -16,41 +16,21 @@
 
 <resources>
 
-    <style name="Base.V14.Theme.AppCompat" parent="Base.V11.Theme.AppCompat" />
-    <style name="Base.V14.Theme.AppCompat.Light" parent="Base.V11.Theme.AppCompat.Light" />
-    <style name="Base.V14.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog" />
-    <style name="Base.V14.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog" />
+    <style name="Platform.AppCompat" parent="Platform.V14.AppCompat" />
+    <style name="Platform.AppCompat.Light" parent="Platform.V14.AppCompat.Light" />
 
-    <style name="Base.Theme.AppCompat" parent="Base.V14.Theme.AppCompat">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
+    <style name="Platform.V14.AppCompat" parent="Platform.V12.AppCompat">
         <item name="android:actionModeSelectAllDrawable">?actionModeSelectAllDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
+
+        <item name="android:listPreferredItemPaddingLeft">@dimen/abc_list_item_padding_horizontal_material</item>
+        <item name="android:listPreferredItemPaddingRight">@dimen/abc_list_item_padding_horizontal_material</item>
     </style>
 
-    <style name="Base.Theme.AppCompat.Light" parent="Base.V14.Theme.AppCompat.Light">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
+    <style name="Platform.V14.AppCompat.Light" parent="Platform.V12.AppCompat.Light">
         <item name="android:actionModeSelectAllDrawable">?actionModeSelectAllDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
-    </style>
 
-    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V14.Theme.AppCompat.Dialog">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeSelectAllDrawable">?actionModeSelectAllDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
-    </style>
-
-    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V14.Theme.AppCompat.Light.Dialog">
-        <item name="android:actionModeCutDrawable">?actionModeCutDrawable</item>
-        <item name="android:actionModeCopyDrawable">?actionModeCopyDrawable</item>
-        <item name="android:actionModePasteDrawable">?actionModePasteDrawable</item>
-        <item name="android:actionModeSelectAllDrawable">?actionModeSelectAllDrawable</item>
-        <item name="android:actionModeShareDrawable">?actionModeShareDrawable</item>
+        <item name="android:listPreferredItemPaddingLeft">@dimen/abc_list_item_padding_horizontal_material</item>
+        <item name="android:listPreferredItemPaddingRight">@dimen/abc_list_item_padding_horizontal_material</item>
     </style>
 
 </resources>
diff --git a/v7/appcompat/res/values-v17/styles_rtl.xml b/v7/appcompat/res/values-v17/styles_rtl.xml
index 0c7d861..d89a63d 100644
--- a/v7/appcompat/res/values-v17/styles_rtl.xml
+++ b/v7/appcompat/res/values-v17/styles_rtl.xml
@@ -47,14 +47,9 @@
         <item name="android:paddingEnd">8dp</item>
     </style>
 
-    <style name="RtlOverlay.Widget.AppCompat.ActionButton.CloseMode" parent="Base.Widget.AppCompat.ActionButton.CloseMode">
-        <item name="android:paddingStart">8dp</item>
-        <item name="android:layout_marginEnd">16dp</item>
-    </style>
-
     <style name="RtlOverlay.Widget.AppCompat.ActionButton.Overflow" parent="Base.Widget.AppCompat.ActionButton.Overflow">
-        <item name="android:paddingStart">0dp</item>
-        <item name="android:paddingEnd">12dp</item>
+        <item name="android:paddingStart">@dimen/abc_action_bar_overflow_padding_start_material</item>
+        <item name="android:paddingEnd">@dimen/abc_action_bar_overflow_padding_end_material</item>
     </style>
 
     <style name="RtlOverlay.Widget.AppCompat.PopupMenuItem" parent="android:Widget">
@@ -70,4 +65,8 @@
         <item name="android:textAlignment">viewStart</item>
     </style>
 
+    <style name="RtlOverlay.Widget.AppCompat.Toolbar.Button.Navigation" parent="Base.Widget.AppCompat.Toolbar.Button.Navigation">
+        <item name="android:paddingStart">@dimen/abc_action_bar_navigation_padding_start_material</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v18/dimens.xml b/v7/appcompat/res/values-v18/dimens.xml
new file mode 100644
index 0000000..bb784b7
--- /dev/null
+++ b/v7/appcompat/res/values-v18/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!-- Since SwitchCompat can use optical insets on v18+, reset the manual padding -->
+    <dimen name="abc_switch_padding">0px</dimen>
+
+</resources>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 648dfd2..aac01dc 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -78,6 +78,7 @@
 
     <style name="Base.Widget.AppCompat.ActionButton.CloseMode"
            parent="android:Widget.Material.ActionButton.CloseMode">
+        <item name="android:minWidth">56dp</item>
     </style>
 
     <style name="Base.Widget.AppCompat.ActionButton.Overflow"
@@ -112,6 +113,8 @@
 
     <style name="Base.Widget.AppCompat.Spinner" parent="android:Widget.Material.Spinner" />
 
+    <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
+
     <style name="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" parent="android:Widget.Material.Spinner">
         <item name="spinnerMode">dropdown</item>
         <item name="disableChildrenWhenDisabled">true</item>
@@ -162,16 +165,30 @@
     <!-- Search View result styles -->
 
     <style name="Base.TextAppearance.AppCompat.SearchResult.Title"
-           parent="@android:TextAppearance.Material.SearchResult.Title">
+           parent="android:TextAppearance.Material.SearchResult.Title">
     </style>
 
     <style name="Base.TextAppearance.AppCompat.SearchResult.Subtitle"
-           parent="@android:TextAppearance.Material.SearchResult.Subtitle">
+           parent="android:TextAppearance.Material.SearchResult.Subtitle">
     </style>
 
     <style name="Base.Widget.AppCompat.AutoCompleteTextView" parent="android:Widget.Material.AutoCompleteTextView" />
 
-    <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView" parent="android:Widget.Material.AutoCompleteTextView" />
+    <style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.Material.RatingBar" />
+
+    <style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button" />
+
+    <style name="Base.Widget.AppCompat.Button.Small" parent="android:Widget.Material.Button.Small" />
+
+    <style name="Base.Widget.AppCompat.Button.Borderless" parent="android:Widget.Material.Button.Borderless" />
+
+    <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+
+    <style name="Base.Widget.AppCompat.ButtonBar" parent="android:Widget.Material.ButtonBar" />
+
+    <style name="Base.Widget.AppCompat.CompoundButton.CheckBox" parent="android:Widget.Material.CompoundButton.CheckBox" />
+
+    <style name="Base.Widget.AppCompat.CompoundButton.RadioButton" parent="android:Widget.Material.CompoundButton.RadioButton" />
 
     <!-- Progress Bar -->
 
@@ -183,13 +200,8 @@
            parent="android:Widget.Material.ProgressBar">
     </style>
 
-    <!-- TODO. Needs updating for Material -->
-    <style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
-        <item name="android:gravity">center</item>
-        <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
-        <item name="android:divider">?attr/dividerVertical</item>
-        <item name="android:showDividers">middle</item>
-        <item name="android:dividerPadding">6dip</item>
-    </style>
+    <style name="Base.Widget.AppCompat.TextView.SpinnerItem" parent="android:Widget.Material.TextView.SpinnerItem" />
+
+    <style name="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="android:TextAppearance.Material.Widget.TextView.SpinnerItem" />
 
 </resources>
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
index 7392b30..b4ed97b 100644
--- a/v7/appcompat/res/values-v21/themes_base.xml
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -26,25 +26,20 @@
     <style name="Platform.AppCompat" parent="android:Theme.Material">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
+
+        <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
+        <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
     </style>
 
     <style name="Platform.AppCompat.Light" parent="android:Theme.Material.Light">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
-    </style>
 
-    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Material.Dialog">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowActionBar">false</item>
-    </style>
-
-    <style name="Platform.AppCompat.Light.Dialog" parent="android:Theme.Material.Light.Dialog">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowActionBar">false</item>
+        <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
+        <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
     </style>
 
     <style name="Base.Theme.AppCompat" parent="Base.V21.Theme.AppCompat" />
-
     <style name="Base.Theme.AppCompat.Light" parent="Base.V21.Theme.AppCompat.Light" />
 
     <style name="Base.V21.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -122,6 +117,8 @@
     </style>
 
     <style name="Base.V21.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog">
+        <item name="android:windowElevation">@dimen/abc_floating_window_z</item>
+
         <!-- Action Bar styling attributes -->
         <item name="actionBarSize">?android:attr/actionBarSize</item>
         <item name="actionBarDivider">?android:attr/actionBarDivider</item>
@@ -159,6 +156,8 @@
     </style>
 
     <style name="Base.V21.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog">
+        <item name="android:windowElevation">@dimen/abc_floating_window_z</item>
+
         <!-- Action Bar styling attributes -->
         <item name="actionBarSize">?android:attr/actionBarSize</item>
         <item name="actionBarDivider">?android:attr/actionBarDivider</item>
@@ -196,15 +195,19 @@
     </style>
 
     <style name="Base.Theme.AppCompat.Dialog" parent="Base.V21.Theme.AppCompat.Dialog" />
-
     <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V21.Theme.AppCompat.Light.Dialog" />
 
     <style name="Base.ThemeOverlay.AppCompat" parent="android:ThemeOverlay.Material">
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
     </style>
 
     <style name="Base.ThemeOverlay.AppCompat.Dark" parent="android:ThemeOverlay.Material.Dark">
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
 
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">false</item>
     </style>
@@ -212,18 +215,31 @@
     <style name="Base.ThemeOverlay.AppCompat.Light" parent="android:ThemeOverlay.Material.Light">
         <item name="colorControlHighlight">@color/ripple_material_light</item>
 
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">true</item>
     </style>
 
     <style name="Base.ThemeOverlay.AppCompat.ActionBar" parent="android:ThemeOverlay.Material.ActionBar">
         <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
     </style>
 
     <style name="Base.ThemeOverlay.AppCompat.Dark.ActionBar" parent="android:ThemeOverlay.Material.Dark.ActionBar">
         <item name="colorControlNormal">?android:attr/textColorPrimary</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
 
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
+
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">false</item>
     </style>
diff --git a/v7/appcompat/res/values-vi/strings.xml b/v7/appcompat/res/values-vi/strings.xml
index 21dd883..9cf34c2 100644
--- a/v7/appcompat/res/values-vi/strings.xml
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Điều hướng về trang chủ"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Điều hướng lên trên"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Thêm tùy chọn"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Thu gọn"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tìm kiếm"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Tìm kiếm…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Tìm kiếm truy vấn"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Xóa truy vấn"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Gửi truy vấn"</string>
diff --git a/v7/appcompat/res/values-zh-rCN/strings.xml b/v7/appcompat/res/values-zh-rCN/strings.xml
index 54e2c86..a0b492a 100644
--- a/v7/appcompat/res/values-zh-rCN/strings.xml
+++ b/v7/appcompat/res/values-zh-rCN/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"转到主屏幕"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"转到上一层级"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多选项"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收起"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s - %2$s:%3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜索"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"搜索…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜索查询"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查询"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查询"</string>
diff --git a/v7/appcompat/res/values-zh-rHK/strings.xml b/v7/appcompat/res/values-zh-rHK/strings.xml
index e35d4651..2e37307 100644
--- a/v7/appcompat/res/values-zh-rHK/strings.xml
+++ b/v7/appcompat/res/values-zh-rHK/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽主頁"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收合"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s (%2$s):%3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"搜尋…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜尋查詢"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查詢"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查詢"</string>
diff --git a/v7/appcompat/res/values-zh-rTW/strings.xml b/v7/appcompat/res/values-zh-rTW/strings.xml
index 24d530c..41a9401 100644
--- a/v7/appcompat/res/values-zh-rTW/strings.xml
+++ b/v7/appcompat/res/values-zh-rTW/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽首頁"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"收合"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s:%2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s - %2$s:%3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"搜尋…"</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜尋查詢"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查詢"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查詢"</string>
diff --git a/v7/appcompat/res/values-zu/strings.xml b/v7/appcompat/res/values-zu/strings.xml
index a6a06ab..48d586b 100644
--- a/v7/appcompat/res/values-zu/strings.xml
+++ b/v7/appcompat/res/values-zu/strings.xml
@@ -20,9 +20,11 @@
     <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zulazulela ekhaya"</string>
     <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Zulazulela phezulu"</string>
     <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Izinketho eziningi"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Goqa"</string>
     <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
     <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
     <string name="abc_searchview_description_search" msgid="8264924765203268293">"Sesha"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Iyasesha..."</string>
     <string name="abc_searchview_description_query" msgid="2550479030709304392">"Umbuzo wosesho"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Sula inkinga"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Hambisa umbuzo"</string>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 896a80a..62c6085 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -42,6 +42,9 @@
              in place of the usual title bar. -->
         <attr name="windowActionBar" format="boolean" />
 
+        <!-- Flag indicating whether there should be no title on this window. -->
+        <attr name="windowNoTitle" format="boolean" />
+
         <!-- Flag indicating whether this window's Action Bar should overlay
              application content. Does nothing if the window would not
              have an Action Bar. -->
@@ -70,6 +73,7 @@
         <attr name="windowFixedHeightMajor" format="dimension|fraction" />
 
         <attr name="android:windowIsFloating" />
+        <attr name="android:windowAnimationStyle" />
 
         <!-- =================== -->
         <!-- Action bar styles   -->
@@ -162,6 +166,17 @@
 
 
         <!-- =================== -->
+        <!-- Dialog styles -->
+        <!-- =================== -->
+        <eat-comment />
+
+        <!-- Theme to use for dialogs spawned from this theme. -->
+        <attr name="dialogTheme" format="reference" />
+        <!-- Preferred padding for dialog content. -->
+        <attr name="dialogPreferredPadding" format="dimension" />
+
+
+        <!-- =================== -->
         <!-- Other widget styles -->
         <!-- =================== -->
         <eat-comment />
@@ -181,11 +196,9 @@
         <!-- Default action button style. -->
         <attr name="actionButtonStyle" format="reference"/>
 
-        <!-- A style that may be applied to horizontal LinearLayouts
-         to form a button bar. -->
+        <!-- Style for button bars -->
         <attr name="buttonBarStyle" format="reference"/>
-        <!-- A style that may be applied to Buttons placed within a
-             LinearLayout with the style buttonBarStyle to form a button bar. -->
+        <!-- Style for buttons within button bars -->
         <attr name="buttonBarButtonStyle" format="reference"/>
         <!-- A style that may be applied to buttons or other selectable items
              that should react to pressed and focus states, but that do not
@@ -431,6 +444,35 @@
              always request focus regardless of this view.  It only impacts where
              focus navigation will try to move focus. -->
         <attr name="android:focusable" />
+
+        <!-- Deprecated. -->
+        <attr name="theme" format="reference" />
+
+        <!-- Specifies a theme override for a view. When a theme override is set, the
+             view will be inflated using a {@link android.content.Context} themed with
+             the specified resource. -->
+        <attr name="android:theme" />
+
+        <!-- Tint to apply to the background. -->
+        <attr name="backgroundTint" format="color" />
+
+        <!-- Blending mode used to apply the background tint. -->
+        <attr name="backgroundTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+        </attr>
     </declare-styleable>
 
     <declare-styleable name="MenuView">
@@ -626,6 +668,8 @@
         <attr name="goIcon" format="reference" />
         <!-- Search icon -->
         <attr name="searchIcon" format="reference" />
+        <!-- Search icon displayed as a text field hint -->
+        <attr name="searchHintIcon" format="reference" />
         <!-- Voice button icon -->
         <attr name="voiceIcon" format="reference" />
         <!-- Commit icon shown in the query suggestion row -->
@@ -655,6 +699,7 @@
     <declare-styleable name="CompatTextView">
         <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
         <attr name="textAllCaps" format="reference|boolean" />
+        <attr name="android:textAppearance" />
     </declare-styleable>
 
     <declare-styleable name="LinearLayoutCompat">
@@ -718,19 +763,9 @@
         <attr name="contentInsetRight" />
         <attr name="maxButtonHeight" format="dimension" />
 
-        <!-- Specifies a theme override for a view. When a theme override is set, the
-             view will be inflated using a {@link android.content.Context} themed with
-             the specified resource. During XML inflation, any child views under the
-             view with a theme override will inherit the themed context. -->
-        <attr name="theme" format="reference" />
-
-        <attr name="buttonGravity">
-            <!-- Push object to the top of its container, not changing its size. -->
-            <flag name="top" value="0x30" />
-            <!-- Push object to the bottom of its container, not changing its size. -->
-            <flag name="bottom" value="0x50" />
-        </attr>
         <attr name="collapseIcon" format="reference" />
+        <!-- Text to set as the content description for the collapse button. -->
+        <attr name="collapseContentDescription" format="string" />
         <!-- Reference to a theme that should be used to inflate popups
              shown by widgets in the toolbar. -->
         <attr name="popupTheme" />
@@ -817,4 +852,12 @@
         <attr name="showText" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="TextAppearance">
+        <attr name="android:textSize" />
+        <attr name="android:textColor" />
+        <attr name="android:textStyle" />
+        <attr name="android:typeface" />
+        <attr name="textAllCaps" />
+    </declare-styleable>
+
 </resources>
diff --git a/v7/appcompat/res/values/colors_material.xml b/v7/appcompat/res/values/colors_material.xml
index 94448b5..6b3cca5 100644
--- a/v7/appcompat/res/values/colors_material.xml
+++ b/v7/appcompat/res/values/colors_material.xml
@@ -23,12 +23,12 @@
     <color name="background_floating_material_light">#ffeeeeee</color>
 
     <color name="primary_material_dark">#ff212121</color>
-    <color name="primary_material_light">#ffbdbdbd</color>
+    <color name="primary_material_light">#ffefefef</color>
     <color name="primary_dark_material_dark">#ff000000</color>
     <color name="primary_dark_material_light">#ff757575</color>
 
-    <color name="ripple_material_dark">#40ffffff</color>
-    <color name="ripple_material_light">#40000000</color>
+    <color name="ripple_material_dark">#4dffffff</color>
+    <color name="ripple_material_light">#1f000000</color>
 
     <color name="accent_material_light">@color/material_deep_teal_500</color>
     <color name="accent_material_dark">@color/material_deep_teal_200</color>
@@ -38,6 +38,8 @@
 
     <color name="switch_thumb_normal_material_dark">#ffbdbdbd</color>
     <color name="switch_thumb_normal_material_light">#fff1f1f1</color>
+    <color name="switch_thumb_disabled_material_dark">#ff616161</color>
+    <color name="switch_thumb_disabled_material_light">#ffbdbdbd</color>
 
     <color name="bright_foreground_material_dark">@android:color/white</color>
     <color name="bright_foreground_material_light">@android:color/black</color>
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index a57f2e4..be6a7a1 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -32,4 +32,10 @@
          it should be disabled in that locale's resources. -->
     <bool name="abc_config_actionMenuItemAllCaps">true</bool>
 
+    <!-- The duration (in milliseconds) of the activity open/close and fragment open/close animations. -->
+    <integer name="abc_config_activityShortDur">150</integer>
+    <integer name="abc_config_activityDefaultDur">220</integer>
+
+    <bool name="abc_config_closeDialogWhenTouchOutside">true</bool>
+
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 54baac3..fb0d94c 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -58,9 +58,30 @@
          (the screen is in landscape). This may be either a fraction or a dimension.-->
     <item type="dimen" name="dialog_fixed_height_minor">100%</item>
 
+    <dimen name="abc_button_inset_vertical_material">6dp</dimen>
+    <dimen name="abc_button_inset_horizontal_material">@dimen/abc_control_inset_material</dimen>
+    <!-- Default inner padding within buttons -->
+    <dimen name="abc_button_padding_vertical_material">@dimen/abc_control_padding_material</dimen>
+    <dimen name="abc_button_padding_horizontal_material">8dp</dimen>
+
     <!-- Default insets (outer padding) around controls -->
     <dimen name="abc_control_inset_material">4dp</dimen>
     <!-- Default inner padding within controls -->
     <dimen name="abc_control_padding_material">4dp</dimen>
+    <!-- Default rounded corner for controls -->
+    <dimen name="abc_control_corner_material">2dp</dimen>
+
+    <dimen name="abc_edit_text_inset_horizontal_material">4dp</dimen>
+    <dimen name="abc_edit_text_inset_top_material">10dp</dimen>
+    <dimen name="abc_edit_text_inset_bottom_material">7dp</dimen>
+
+    <!-- Since optical insets are not available pre-v18, we add a small amount of padding -->
+    <dimen name="abc_switch_padding">3dp</dimen>
+
+    <dimen name="abc_dialog_padding_material">24dp</dimen>
+    <dimen name="abc_dialog_padding_top_material">18dp</dimen>
+
+    <!-- Dialog button bar height -->
+    <dimen name="abc_alert_dialog_button_bar_height">48dp</dimen>
 
 </resources>
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
index a620b31..6f5f1f8 100644
--- a/v7/appcompat/res/values/dimens_material.xml
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -20,6 +20,8 @@
     <dimen name="abc_action_bar_default_height_material">56dp</dimen>
     <!-- Default padding of an action bar. -->
     <dimen name="abc_action_bar_default_padding_material">4dp</dimen>
+    <!-- Default content inset of an action bar. -->
+    <dimen name="abc_action_bar_content_inset_material">16dp</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding_material">16dp</dimen>
     <!-- Top margin for action bar subtitles -->
@@ -27,6 +29,17 @@
     <!-- Bottom margin for action bar subtitles -->
     <dimen name="abc_action_bar_subtitle_bottom_margin_material">5dp</dimen>
 
+    <!-- Default padding for list items. This should match the action bar
+         content inset so that ListActivity items line up correctly. -->
+    <dimen name="abc_list_item_padding_horizontal_material">@dimen/abc_action_bar_content_inset_material</dimen>
+
+    <!-- Padding to add to the start of the overflow action button. -->
+    <dimen name="abc_action_bar_navigation_padding_start_material">0dp</dimen>
+    <!-- Padding to add to the start of the overflow action button. -->
+    <dimen name="abc_action_bar_overflow_padding_start_material">6dp</dimen>
+    <!-- Padding to add to the end of the overflow action button. -->
+    <dimen name="abc_action_bar_overflow_padding_end_material">10dp</dimen>
+
     <dimen name="abc_action_button_min_width_overflow_material">36dp</dimen>
     <dimen name="abc_action_button_min_width_material">48dp</dimen>
     <dimen name="abc_action_button_min_height_material">48dp</dimen>
@@ -50,4 +63,9 @@
     <dimen name="abc_text_size_medium_material">18sp</dimen>
     <dimen name="abc_text_size_small_material">14sp</dimen>
 
+    <dimen name="abc_floating_window_z">16dp</dimen>
+
+    <item name="abc_disabled_alpha_material_light" format="float" type="dimen">0.26</item>
+    <item name="abc_disabled_alpha_material_dark" format="float" type="dimen">0.30</item>
+
 </resources>
diff --git a/v7/appcompat/res/values/strings.xml b/v7/appcompat/res/values/strings.xml
index 765833d..87cfd29 100644
--- a/v7/appcompat/res/values/strings.xml
+++ b/v7/appcompat/res/values/strings.xml
@@ -24,6 +24,9 @@
     <!-- Content description for the action menu overflow button. [CHAR LIMIT=NONE] -->
     <string name="abc_action_menu_overflow_description">More options</string>
 
+    <!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
+    <string name="abc_toolbar_collapse_description">Collapse</string>
+
     <!-- Formatting string for describing the action bar's title/home/up affordance.
          This is a single tappable "button" that includes the app icon, the Up indicator
          (usually a "<" chevron) and the window title text.
@@ -38,6 +41,8 @@
 
     <!-- SearchView accessibility description for search button [CHAR LIMIT=NONE] -->
     <string name="abc_searchview_description_search">Search</string>
+    <!-- Default hint text for the system-wide search UI's text field. [CHAR LIMIT=30] -->
+    <string name="abc_search_hint">Search…</string>
     <!-- SearchView accessibility description for search text field [CHAR LIMIT=NONE] -->
     <string name="abc_searchview_description_query">Search query</string>
     <!-- SearchView accessibility description for clear button [CHAR LIMIT=NONE] -->
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 1b8b53b..13d910e 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -60,8 +60,7 @@
 
     <style name="Widget.AppCompat.ActionButton" parent="Base.Widget.AppCompat.ActionButton" />
 
-    <!-- This style has an extra indirection to properly set RTL attributes. See styles_rtl.xml -->
-    <style name="Widget.AppCompat.ActionButton.CloseMode" parent="RtlOverlay.Widget.AppCompat.ActionButton.CloseMode" />
+    <style name="Widget.AppCompat.ActionButton.CloseMode" parent="Base.Widget.AppCompat.ActionButton.CloseMode" />
 
     <style name="Widget.AppCompat.ActionButton.Overflow"
            parent="RtlOverlay.Widget.AppCompat.ActionButton.Overflow" />
@@ -120,6 +119,8 @@
 
     <style name="Widget.AppCompat.Spinner" parent="Base.Widget.AppCompat.Spinner" />
 
+    <style name="Widget.AppCompat.Spinner.Underlined" parent="Base.Widget.AppCompat.Spinner.Underlined" />
+
     <style name="Widget.AppCompat.Spinner.DropDown" />
 
     <style name="Widget.AppCompat.Spinner.DropDown.ActionBar" parent="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" />
@@ -187,19 +188,12 @@
            parent="Base.Widget.AppCompat.AutoCompleteTextView">
     </style>
 
-    <style name="Widget.AppCompat.Light.AutoCompleteTextView"
-           parent="Base.Widget.AppCompat.Light.AutoCompleteTextView">
-    </style>
-
     <style name="Widget.AppCompat.ActivityChooserView"
            parent="Base.Widget.AppCompat.ActivityChooserView">
     </style>
 
-    <style name="Widget.AppCompat.Light.ActivityChooserView"
-           parent="Base.Widget.AppCompat.Light.ActivityChooserView">
-    </style>
-
     <style name="Widget.AppCompat.SearchView" parent="Base.Widget.AppCompat.SearchView" />
+    <style name="Widget.AppCompat.SearchView.ActionBar" parent="Base.Widget.AppCompat.SearchView.ActionBar" />
 
     <style name="Widget.AppCompat.EditText"
            parent="Base.Widget.AppCompat.EditText">
@@ -207,12 +201,34 @@
 
     <style name="Widget.AppCompat.CompoundButton.Switch" parent="Base.Widget.AppCompat.CompoundButton.Switch" />
 
+    <style name="Widget.AppCompat.CompoundButton.CheckBox" parent="Base.Widget.AppCompat.CompoundButton.CheckBox" />
+
+    <style name="Widget.AppCompat.CompoundButton.RadioButton" parent="Base.Widget.AppCompat.CompoundButton.RadioButton" />
+
+    <style name="Widget.AppCompat.RatingBar" parent="Base.Widget.AppCompat.RatingBar" />
+
+    <style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button" />
+
+    <style name="Widget.AppCompat.Button.Small" parent="Base.Widget.AppCompat.Button.Small" />
+
+    <style name="Widget.AppCompat.Button.Borderless" parent="Base.Widget.AppCompat.Button.Borderless" />
+
+    <style name="Widget.AppCompat.Button.Borderless.Colored" parent="Base.Widget.AppCompat.Button.Borderless.Colored" />
+
+    <style name="Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" />
+
+    <style name="Widget.AppCompat.ButtonBar" parent="Base.Widget.AppCompat.ButtonBar" />
+
+    <style name="Widget.AppCompat.ButtonBar.AlertDialog" parent="Base.Widget.AppCompat.ButtonBar.AlertDialog" />
+
+    <style name="Widget.AppCompat.TextView.SpinnerItem" parent="Base.Widget.AppCompat.TextView.SpinnerItem" />
+
     <!-- Toolbar -->
 
     <style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
 
     <style name="Widget.AppCompat.Toolbar.Button.Navigation"
-           parent="Base.Widget.AppCompat.Toolbar.Button.Navigation" />
+           parent="RtlOverlay.Widget.AppCompat.Toolbar.Button.Navigation" />
 
     <style name="TextAppearance.Widget.AppCompat.Toolbar.Title"
            parent="Base.TextAppearance.Widget.AppCompat.Toolbar.Title">
@@ -223,8 +239,14 @@
     </style>
 
 
+    <!-- Animation styles -->
     <eat-comment />
+    <style name="Animation.AppCompat.Dialog" parent="Base.Animation.AppCompat.Dialog" />
+    <style name="Animation.AppCompat.DropDownUp" parent="Base.Animation.AppCompat.DropDownUp" />
+
+
     <!-- Text styles -->
+    <eat-comment />
 
     <style name="TextAppearance.AppCompat" parent="Base.TextAppearance.AppCompat" />
 
@@ -272,6 +294,8 @@
 
     <style name="TextAppearance.AppCompat.Widget.Switch" parent="Base.TextAppearance.AppCompat.Widget.Switch" />
 
+    <style name="TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" />
+
     <!--
          The following themes are deprecated.
     -->
@@ -291,5 +315,7 @@
     <style name="Widget.AppCompat.Light.Spinner.DropDown.ActionBar" parent="Widget.AppCompat.Spinner.DropDown.ActionBar" />
     <style name="Widget.AppCompat.Light.ListView.DropDown" parent="Widget.AppCompat.ListView.DropDown" />
     <style name="Widget.AppCompat.Light.ListPopupWindow" parent="Widget.AppCompat.ListPopupWindow" />
+    <style name="Widget.AppCompat.Light.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView" />
+    <style name="Widget.AppCompat.Light.ActivityChooserView" parent="Widget.AppCompat.ActivityChooserView" />
 
 </resources>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index db2cd73..73f81c9 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -36,10 +36,9 @@
         <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
 
-        <item name="progressBarStyle">@style/Widget.AppCompat.ProgressBar.Horizontal</item>
-        <item name="indeterminateProgressStyle">@style/Widget.AppCompat.ProgressBar</item>
-
         <item name="android:gravity">center_vertical</item>
+        <item name="contentInsetStart">@dimen/abc_action_bar_content_inset_material</item>
+        <item name="contentInsetEnd">@dimen/abc_action_bar_content_inset_material</item>
         <item name="elevation">8dp</item>
         <item name="popupTheme">?attr/actionBarPopupTheme</item>
     </style>
@@ -75,6 +74,7 @@
 
     <style name="Base.Widget.AppCompat.ActionButton.CloseMode">
         <item name="android:background">?attr/selectableItemBackgroundBorderless</item>
+        <item name="android:minWidth">56dp</item>
     </style>
 
     <style name="Base.Widget.AppCompat.ActionButton.Overflow">
@@ -186,6 +186,10 @@
         <item name="android:dropDownVerticalOffset">0dip</item>
     </style>
 
+    <style name="Base.Widget.AppCompat.Spinner.Underlined">
+        <item name="android:background">@drawable/abc_spinner_textfield_background_material</item>
+    </style>
+
     <style name="Base.Widget.AppCompat.Spinner.DropDown.ActionBar" parent="android:Widget">
         <item name="spinnerMode">dropdown</item>
 
@@ -281,28 +285,21 @@
     </style>
 
     <style name="Base.Widget.AppCompat.AutoCompleteTextView" parent="android:Widget.AutoCompleteTextView">
-        <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_dark</item>
+        <item name="android:dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="android:popupBackground">@drawable/abc_popup_background_mtrl_mult</item>
+        <item name="android:background">?attr/editTextBackground</item>
         <item name="android:textColor">?attr/editTextColor</item>
-    </style>
-
-    <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView" parent="android:Widget.AutoCompleteTextView">
-        <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_light</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
     </style>
 
     <style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
         <item name="android:gravity">center</item>
-        <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
+        <item name="android:background">@drawable/abc_ab_share_pack_mtrl_alpha</item>
         <item name="divider">?attr/dividerVertical</item>
         <item name="showDividers">middle</item>
         <item name="dividerPadding">6dip</item>
     </style>
 
-    <style name="Base.Widget.AppCompat.Light.ActivityChooserView"
-           parent="Base.Widget.AppCompat.ActivityChooserView">
-        <item name="android:background">@drawable/abc_ab_share_pack_holo_light</item>
-    </style>
-
     <style name="Base.Widget.AppCompat.PopupWindow" parent="android:Widget.PopupWindow">
     </style>
 
@@ -312,8 +309,8 @@
         <item name="android:minHeight">?attr/actionBarSize</item>
         <item name="titleMargins">4dp</item>
         <item name="maxButtonHeight">56dp</item>
-        <item name="buttonGravity">top</item>
         <item name="collapseIcon">?attr/homeAsUpIndicator</item>
+        <item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
         <item name="contentInsetStart">16dp</item>
     </style>
 
@@ -337,27 +334,49 @@
         <item name="submitBackground">@drawable/abc_textfield_search_material</item>
         <item name="closeIcon">@drawable/abc_ic_clear_mtrl_alpha</item>
         <item name="searchIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
+        <item name="searchHintIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
         <item name="goIcon">@drawable/abc_ic_go_search_api_mtrl_alpha</item>
         <item name="voiceIcon">@drawable/abc_ic_voice_search_api_mtrl_alpha</item>
         <item name="commitIcon">@drawable/abc_ic_commit_search_api_mtrl_alpha</item>
         <item name="suggestionRowLayout">@layout/abc_search_dropdown_item_icons_2line</item>
     </style>
 
+    <style name="Base.Widget.AppCompat.SearchView.ActionBar">
+        <item name="queryBackground">@null</item>
+        <item name="submitBackground">@null</item>
+        <item name="searchHintIcon">@null</item>
+        <item name="queryHint">@string/abc_search_hint</item>
+    </style>
+
     <style name="Base.Widget.AppCompat.EditText" parent="android:Widget.EditText">
         <item name="android:background">?attr/editTextBackground</item>
         <item name="android:textColor">?attr/editTextColor</item>
         <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
     </style>
-
-    <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="">
+    <!-- contains values used in all dpis -->
+    <style name="Base.Widget.AppCompat.DrawerArrowToggle.Common" parent="">
         <item name="color">?android:attr/textColorSecondary</item>
+        <item name="middleBarArrowSize">16dp</item>
+        <item name="spinBars">true</item>
         <item name="thickness">2dp</item>
+        <item name="topBottomBarArrowSize">11.31dp</item>
+    </style>
+
+    <!-- contains values used in all dpis except hdpi and xxhdpi -->
+    <style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="Base.Widget.AppCompat.DrawerArrowToggle.Common">
         <item name="barSize">18dp</item>
         <item name="gapBetweenBars">3dp</item>
-        <item name="topBottomBarArrowSize">11.31dp</item>
-        <item name="middleBarArrowSize">16dp</item>
         <item name="drawableSize">24dp</item>
-        <item name="spinBars">true</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.CompoundButton.CheckBox" parent="android:Widget.CompoundButton.CheckBox">
+        <item name="android:button">?android:attr/listChoiceIndicatorMultiple</item>
+        <item name="android:background">?attr/selectableItemBackgroundBorderless</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.CompoundButton.RadioButton" parent="android:Widget.CompoundButton.RadioButton">
+        <item name="android:button">?android:attr/listChoiceIndicatorSingle</item>
+        <item name="android:background">?attr/selectableItemBackgroundBorderless</item>
     </style>
 
     <style name="Base.Widget.AppCompat.CompoundButton.Switch" parent="android:Widget.CompoundButton">
@@ -366,8 +385,86 @@
         <item name="switchTextAppearance">@style/TextAppearance.AppCompat.Widget.Switch</item>
         <item name="android:background">?attr/selectableItemBackgroundBorderless</item>
         <item name="showText">false</item>
+        <item name="android:padding">@dimen/abc_switch_padding</item>
     </style>
 
     <style name="Base.TextAppearance.AppCompat.Widget.Switch" parent="TextAppearance.AppCompat.Button" />
 
+    <style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.RatingBar">
+        <item name="android:progressDrawable">@drawable/abc_ratingbar_full_material</item>
+        <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_full_material</item>
+    </style>
+
+    <!-- Bordered ink button -->
+    <style name="Base.Widget.AppCompat.Button" parent="android:Widget">
+        <item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:minWidth">88dip</item>
+        <item name="android:focusable">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:gravity">center_vertical|center_horizontal</item>
+    </style>
+
+    <!-- Small bordered ink button -->
+    <style name="Base.Widget.AppCompat.Button.Small">
+        <item name="android:minHeight">48dip</item>
+        <item name="android:minWidth">48dip</item>
+    </style>
+
+    <!-- Borderless ink button -->
+    <style name="Base.Widget.AppCompat.Button.Borderless">
+        <item name="android:background">@drawable/abc_btn_borderless_material</item>
+    </style>
+
+    <!-- Colored borderless ink button -->
+    <style name="Base.Widget.AppCompat.Button.Borderless.Colored">
+        <item name="android:textColor">?attr/colorAccent</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
+        <item name="android:minWidth">64dp</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:minHeight">@dimen/abc_alert_dialog_button_bar_height</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.TextView.SpinnerItem" parent="android:Widget.TextView.SpinnerItem">
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Widget.TextView.SpinnerItem</item>
+        <item name="android:paddingLeft">8dp</item>
+        <item name="android:paddingRight">8dp</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.TextView.SpinnerItem" parent="TextAppearance.AppCompat.Menu" />
+
+    <style name="Base.DialogWindowTitleBackground.AppCompat" parent="android:Widget">
+        <item name="android:background">@null</item>
+        <item name="android:paddingLeft">?attr/dialogPreferredPadding</item>
+        <item name="android:paddingRight">?attr/dialogPreferredPadding</item>
+        <item name="android:paddingTop">@dimen/abc_dialog_padding_top_material</item>
+    </style>
+
+    <style name="Base.DialogWindowTitle.AppCompat" parent="android:Widget">
+        <item name="android:maxLines">1</item>
+        <item name="android:scrollHorizontally">true</item>
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Title</item>
+    </style>
+
+    <style name="Base.Animation.AppCompat.Dialog" parent="android:Animation">
+        <item name="android:windowEnterAnimation">@anim/abc_popup_enter</item>
+        <item name="android:windowExitAnimation">@anim/abc_popup_exit</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.ButtonBar" parent="android:Widget">
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.ButtonBar.AlertDialog">
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="Base.Animation.AppCompat.DropDownUp" parent="android:Animation">
+        <item name="android:windowEnterAnimation">@anim/abc_grow_fade_in_from_bottom</item>
+        <item name="android:windowExitAnimation">@anim/abc_shrink_fade_out_from_bottom</item>
+    </style>
+
 </resources>
diff --git a/v7/appcompat/res/values/styles_base_text.xml b/v7/appcompat/res/values/styles_base_text.xml
index 92a02bb..78e119c 100644
--- a/v7/appcompat/res/values/styles_base_text.xml
+++ b/v7/appcompat/res/values/styles_base_text.xml
@@ -58,8 +58,6 @@
     <style name="Base.TextAppearance.AppCompat.Title.Inverse">
         <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
         <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
-        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
-        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
     </style>
 
     <style name="Base.TextAppearance.AppCompat.Subhead">
@@ -70,8 +68,6 @@
     <style name="Base.TextAppearance.AppCompat.Subhead.Inverse">
         <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
         <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
-        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
-        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
     </style>
 
     <style name="Base.TextAppearance.AppCompat.Body2">
diff --git a/v7/appcompat/res/values/styles_rtl.xml b/v7/appcompat/res/values/styles_rtl.xml
index fad1291..5320800 100644
--- a/v7/appcompat/res/values/styles_rtl.xml
+++ b/v7/appcompat/res/values/styles_rtl.xml
@@ -47,14 +47,9 @@
         <item name="android:paddingRight">8dp</item>
     </style>
 
-    <style name="RtlOverlay.Widget.AppCompat.ActionButton.CloseMode" parent="Base.Widget.AppCompat.ActionButton.CloseMode">
-        <item name="android:paddingLeft">8dp</item>
-        <item name="android:layout_marginRight">16dp</item>
-    </style>
-
     <style name="RtlOverlay.Widget.AppCompat.ActionButton.Overflow" parent="Base.Widget.AppCompat.ActionButton.Overflow">
-        <item name="android:paddingLeft">0dp</item>
-        <item name="android:paddingRight">12dp</item>
+        <item name="android:paddingLeft">@dimen/abc_action_bar_overflow_padding_start_material</item>
+        <item name="android:paddingRight">@dimen/abc_action_bar_overflow_padding_end_material</item>
     </style>
 
     <style name="RtlOverlay.Widget.AppCompat.PopupMenuItem" parent="android:Widget">
@@ -69,4 +64,8 @@
         <item name="android:layout_alignParentLeft">true</item>
     </style>
 
+    <style name="RtlOverlay.Widget.AppCompat.Toolbar.Button.Navigation" parent="Base.Widget.AppCompat.Toolbar.Button.Navigation">
+        <item name="android:paddingLeft">@dimen/abc_action_bar_navigation_padding_start_material</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/themes.xml b/v7/appcompat/res/values/themes.xml
index 05b8657..5742251 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -36,12 +36,12 @@
 
     <style name="Theme.AppCompat.NoActionBar">
         <item name="windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
+        <item name="windowNoTitle">true</item>
     </style>
 
     <style name="Theme.AppCompat.Light.NoActionBar">
         <item name="windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
+        <item name="windowNoTitle">true</item>
     </style>
 
     <style name="Theme.AppCompat.DialogWhenLarge"
@@ -57,9 +57,8 @@
     <style name="Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light.Dialog" />
 
     <!-- Menu/item attributes -->
-    <style name="Theme.AppCompat.CompactMenu"
-           parent="Base.Theme.AppCompat.CompactMenu">
-    </style>
+    <style name="Theme.AppCompat.CompactMenu" parent="Base.Theme.AppCompat.CompactMenu" />
+
 
     <style name="ThemeOverlay.AppCompat" parent="Base.ThemeOverlay.AppCompat" />
 
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 3c0ee65..025352d 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -25,15 +25,12 @@
     <style name="Platform.AppCompat" parent="android:Theme">
         <item name="android:windowNoTitle">true</item>
 
-        <item name="buttonBarStyle">@android:style/ButtonBar</item>
-        <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
-
         <!-- Window colors -->
         <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
         <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
         <item name="android:colorBackground">@color/background_material_dark</item>
         <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
-        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:disabledAlpha">@dimen/abc_disabled_alpha_material_dark</item>
         <item name="android:backgroundDimAmount">0.6</item>
         <item name="android:windowBackground">@color/background_material_dark</item>
 
@@ -47,7 +44,7 @@
         <item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
         <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
         <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
-        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
         <item name="android:textColorLink">@color/link_text_material_dark</item>
 
         <!-- Text styles -->
@@ -61,6 +58,7 @@
         <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
 
         <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+        <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
         <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
 
         <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
@@ -70,15 +68,12 @@
     <style name="Platform.AppCompat.Light" parent="android:Theme.Light">
         <item name="android:windowNoTitle">true</item>
 
-        <item name="buttonBarStyle">@android:style/ButtonBar</item>
-        <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
-
         <!-- Window colors -->
         <item name="android:colorForeground">@color/bright_foreground_material_light</item>
         <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
         <item name="android:colorBackground">@color/background_material_light</item>
         <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
-        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:disabledAlpha">@dimen/abc_disabled_alpha_material_light</item>
         <item name="android:backgroundDimAmount">0.6</item>
         <item name="android:windowBackground">@color/background_material_light</item>
 
@@ -107,59 +102,13 @@
         <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
 
         <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
+        <item name="android:spinnerItemStyle">@style/Widget.AppCompat.TextView.SpinnerItem</item>
         <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
 
         <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
         <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
     </style>
 
-    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Dialog">
-        <item name="android:windowNoTitle">true</item>
-
-        <item name="buttonBarStyle">@android:style/ButtonBar</item>
-        <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
-
-        <!-- Window colors -->
-        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
-        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
-        <item name="android:colorBackground">@color/background_material_dark</item>
-        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
-        <item name="android:disabledAlpha">0.5</item>
-        <item name="android:backgroundDimAmount">0.6</item>
-        <item name="android:windowBackground">@color/background_material_dark</item>
-
-        <!-- Text colors -->
-        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
-        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
-        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
-        <item name="android:textColorSecondary">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorSecondaryInverse">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorTertiary">@color/abc_secondary_text_material_dark</item>
-        <item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
-        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
-        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
-        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
-        <item name="android:textColorLink">@color/link_text_material_dark</item>
-
-        <!-- Text styles -->
-        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
-        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
-        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
-        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
-        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
-        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
-        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
-        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
-
-        <item name="android:spinnerStyle">@style/Widget.AppCompat.Spinner</item>
-        <item name="android:dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <item name="android:listChoiceIndicatorSingle">@drawable/abc_btn_radio_material</item>
-        <item name="android:listChoiceIndicatorMultiple">@drawable/abc_btn_check_material</item>
-    </style>
-
-    <style name="Platform.AppCompat.Light.Dialog" parent="Platform.AppCompat.Dialog" />
-
     <!-- Themes in the "Base.Theme" family vary based on the current platform
          version to provide the correct basis on each device. You probably don't
          want to use them directly in your apps.
@@ -229,8 +178,8 @@
         <item name="listPreferredItemHeight">64dp</item>
         <item name="listPreferredItemHeightSmall">48dp</item>
         <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">16dip</item>
-        <item name="listPreferredItemPaddingRight">16dip</item>
+        <item name="listPreferredItemPaddingLeft">@dimen/abc_list_item_padding_horizontal_material</item>
+        <item name="listPreferredItemPaddingRight">@dimen/abc_list_item_padding_horizontal_material</item>
 
         <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
         <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
@@ -260,6 +209,7 @@
         <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
         <item name="editTextBackground">@drawable/abc_edit_text_material</item>
         <item name="editTextColor">?android:attr/textColorPrimary</item>
+        <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
 
         <!-- Color palette -->
         <item name="colorPrimaryDark">@color/primary_dark_material_dark</item>
@@ -269,11 +219,28 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
+        <item name="colorButtonNormal">@color/button_material_dark</item>
+        <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
 
         <item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</item>
 
+        <item name="android:checkboxStyle">@style/Widget.AppCompat.CompoundButton.CheckBox</item>
+        <item name="android:radioButtonStyle">@style/Widget.AppCompat.CompoundButton.RadioButton</item>
         <item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+        <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+        <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+        <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
+
+        <item name="buttonBarStyle">@style/Widget.AppCompat.ButtonBar</item>
+        <item name="buttonBarButtonStyle">@style/Widget.AppCompat.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Dialog attributes -->
+        <item name="dialogTheme">@style/Theme.AppCompat.Dialog</item>
+        <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
@@ -337,8 +304,8 @@
         <item name="listPreferredItemHeight">64dp</item>
         <item name="listPreferredItemHeightSmall">48dp</item>
         <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">16dip</item>
-        <item name="listPreferredItemPaddingRight">16dip</item>
+        <item name="listPreferredItemPaddingLeft">@dimen/abc_list_item_padding_horizontal_material</item>
+        <item name="listPreferredItemPaddingRight">@dimen/abc_list_item_padding_horizontal_material</item>
 
         <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
         <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
@@ -359,7 +326,7 @@
         <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
 
         <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
+        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
 
         <!-- Toolbar styles -->
         <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
@@ -368,6 +335,7 @@
         <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
         <item name="editTextBackground">@drawable/abc_edit_text_material</item>
         <item name="editTextColor">?android:attr/textColorPrimary</item>
+        <item name="android:autoCompleteTextViewStyle">@style/Widget.AppCompat.AutoCompleteTextView</item>
 
         <!-- Color palette -->
         <item name="colorPrimaryDark">@color/primary_dark_material_light</item>
@@ -377,11 +345,28 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_light</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
+        <item name="colorButtonNormal">@color/button_material_light</item>
+        <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
 
         <item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</item>
 
+        <item name="android:checkboxStyle">@style/Widget.AppCompat.CompoundButton.CheckBox</item>
+        <item name="android:radioButtonStyle">@style/Widget.AppCompat.CompoundButton.RadioButton</item>
         <item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+
+        <item name="android:ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+
+        <!-- Button styles -->
+        <item name="android:buttonStyle">@style/Widget.AppCompat.Button</item>
+        <item name="android:buttonStyleSmall">@style/Widget.AppCompat.Button.Small</item>
+        <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item>
+
+        <item name="buttonBarStyle">@style/Widget.AppCompat.ButtonBar</item>
+        <item name="buttonBarButtonStyle">@style/Widget.AppCompat.Button.ButtonBar.AlertDialog</item>
+
+        <!-- Dialog attributes -->
+        <item name="dialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
+        <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
     </style>
 
     <style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -406,114 +391,55 @@
     <style name="Base.Theme.AppCompat.CompactMenu" parent="">
         <item name="android:itemTextAppearance">?android:attr/textAppearanceMedium</item>
         <item name="android:listViewStyle">@style/Widget.AppCompat.ListView.Menu</item>
+        <item name="android:windowAnimationStyle">@style/Animation.AppCompat.DropDownUp</item>
     </style>
 
-    <style name="Base.V7.Theme.AppCompat.Dialog" parent="Platform.AppCompat.Dialog">
-        <item name="windowActionBar">true</item>
-        <item name="windowActionBarOverlay">false</item>
-        <item name="isLightTheme">false</item>
+    <style name="Base.V7.Theme.AppCompat.Dialog" parent="Base.Theme.AppCompat">
+        <item name="android:colorBackground">@color/background_floating_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
 
-        <item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
-        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@style/Base.DialogWindowTitle.AppCompat</item>
+        <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
+        <item name="android:windowBackground">@drawable/abc_dialog_material_background_dark</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowAnimationStyle">@style/Animation.AppCompat.Dialog</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
 
-        <item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
-        <item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
+        <item name="windowActionBar">false</item>
+        <item name="windowActionModeOverlay">true</item>
 
-        <!-- Action Bar Styles -->
-        <item name="actionBarTabStyle">@style/Widget.AppCompat.ActionBar.TabView</item>
-        <item name="actionBarTabBarStyle">@style/Widget.AppCompat.ActionBar.TabBar</item>
-        <item name="actionBarTabTextStyle">@style/Widget.AppCompat.ActionBar.TabText</item>
-        <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
-        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
-        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.PopupMenu.Overflow</item>
-        <item name="actionBarStyle">@style/Widget.AppCompat.ActionBar.Solid</item>
-        <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
-        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
-        <item name="actionBarDivider">?attr/dividerVertical</item>
-        <item name="actionBarItemBackground">?attr/selectableItemBackground</item>
-        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
-        <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
+        <item name="listPreferredItemPaddingLeft">24dip</item>
+        <item name="listPreferredItemPaddingRight">24dip</item>
 
-        <!-- Dropdown Spinner Attributes -->
-        <item name="actionDropDownStyle">@style/Widget.AppCompat.Spinner.DropDown.ActionBar</item>
+        <item name="android:listDivider">@null</item>
+    </style>
 
-        <!-- Action Mode -->
-        <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
-        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
-        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
+    <style name="Base.V7.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light">
+        <item name="android:colorBackground">@color/background_floating_material_light</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
 
-        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
-        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
-        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
-        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@style/Base.DialogWindowTitle.AppCompat</item>
+        <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
+        <item name="android:windowBackground">@drawable/abc_dialog_material_background_light</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowAnimationStyle">@style/Animation.AppCompat.Dialog</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
 
-        <!-- Panel attributes -->
-        <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
-        <item name="panelMenuListTheme">@style/Theme.AppCompat.CompactMenu</item>
-        <item name="panelBackground">@drawable/abc_menu_hardkey_panel_mtrl_mult</item>
-        <item name="android:panelBackground">@android:color/transparent</item>
-        <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_dark</item>
+        <item name="windowActionBar">false</item>
+        <item name="windowActionModeOverlay">true</item>
 
-        <!-- List attributes -->
-        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
-        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
-        <item name="listPreferredItemHeight">64dp</item>
-        <item name="listPreferredItemHeightSmall">48dp</item>
-        <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">16dip</item>
-        <item name="listPreferredItemPaddingRight">16dip</item>
+        <item name="listPreferredItemPaddingLeft">24dip</item>
+        <item name="listPreferredItemPaddingRight">24dip</item>
 
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
-        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
-
-        <!-- Popup Menu styles -->
-        <item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
-        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
-        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
-        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
-        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <!-- SearchView attributes -->
-        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
-        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
-        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
-        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.SearchResult.Title</item>
-        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
-
-        <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
-
-        <!-- Toolbar styles -->
-        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
-        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
-
-        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
-        <item name="editTextBackground">@drawable/abc_edit_text_material</item>
-        <item name="editTextColor">?android:attr/textColorPrimary</item>
-
-        <!-- Color palette -->
-        <item name="colorPrimaryDark">@color/primary_dark_material_dark</item>
-        <item name="colorPrimary">@color/primary_material_dark</item>
-        <item name="colorAccent">@color/accent_material_dark</item>
-
-        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
-        <item name="colorControlActivated">?attr/colorAccent</item>
-        <item name="colorControlHighlight">@color/ripple_material_dark</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
-
-        <item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
+        <item name="android:listDivider">@null</item>
     </style>
 
     <style name="Base.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
-
-    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Dialog" />
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V7.Theme.AppCompat.Light.Dialog" />
 
     <style name="Base.Theme.AppCompat.Dialog.FixedSize">
         <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
@@ -567,7 +493,7 @@
 
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlHighlight">@color/ripple_material_light</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
+        <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
 
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">true</item>
@@ -589,7 +515,7 @@
         <item name="android:textColorTertiaryInverse">@color/abc_secondary_text_material_light</item>
         <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
         <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
-        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>m>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
         <item name="android:textColorLink">@color/link_text_material_dark</item>
 
         <!-- Action Bar styles -->
@@ -603,7 +529,7 @@
 
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
-        <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
+        <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
 
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">false</item>
@@ -611,10 +537,12 @@
 
     <style name="Base.ThemeOverlay.AppCompat.ActionBar">
         <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
     </style>
 
     <style name="Base.ThemeOverlay.AppCompat.Dark.ActionBar">
         <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
     </style>
 
 </resources>
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index c174cb4..d48dd31 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -1059,6 +1059,11 @@
         return false;
     }
 
+    /** @hide **/
+    public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+        return false;
+    }
+
     /** @hide */
     public boolean collapseActionView() {
         return false;
@@ -1350,12 +1355,4 @@
             super(source);
         }
     }
-
-    /**
-     * Interface implemented by entities such as Activities that host action bars.
-     */
-    interface Callback {
-
-        FragmentManager getSupportFragmentManager();
-    }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
index 5fa8cfb..1834681 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
@@ -16,546 +16,9 @@
 
 package android.support.v7.app;
 
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.ActionBarDrawerToggle;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.NavUtils;
-import android.support.v4.app.TaskStackBuilder;
-import android.support.v4.view.WindowCompat;
-import android.support.v7.view.ActionMode;
-import android.support.v7.widget.Toolbar;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-
 /**
- * Base class for activities that use the <a
- * href="{@docRoot}tools/extras/support-library.html">support library</a> action bar features.
- *
- * <p>You can add an {@link ActionBar} to your activity when running on API level 7 or higher
- * by extending this class for your activity and setting the activity theme to
- * {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme.
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- *
- * <p>For information about how to use the action bar, including how to add action items, navigation
- * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
- * Bar</a> API guide.</p>
- * </div>
+ * @deprecated Use {@link android.support.v7.app.AppCompatActivity} instead.
  */
-public class ActionBarActivity extends FragmentActivity implements ActionBar.Callback,
-        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider,
-        android.support.v7.app.ActionBarDrawerToggle.TmpDelegateProvider {
-
-    private ActionBarActivityDelegate mDelegate;
-
-    /**
-     * Support library version of {@link Activity#getActionBar}.
-     *
-     * <p>Retrieve a reference to this activity's ActionBar.
-     *
-     * @return The Activity's ActionBar, or null if it does not have one.
-     */
-    public ActionBar getSupportActionBar() {
-        return getDelegate().getSupportActionBar();
-    }
-
-    /**
-     * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link ActionBar} for this
-     * Activity window.
-     *
-     * <p>When set to a non-null value the {@link #getActionBar()} method will return
-     * an {@link ActionBar} object that can be used to control the given toolbar as if it were
-     * a traditional window decor action bar. The toolbar's menu will be populated with the
-     * Activity's options menu and the navigation button will be wired through the standard
-     * {@link android.R.id#home home} menu select action.</p>
-     *
-     * <p>In order to use a Toolbar within the Activity's window content the application
-     * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
-     *
-     * @param toolbar Toolbar to set as the Activity's action bar
-     */
-    public void setSupportActionBar(@Nullable Toolbar toolbar) {
-        getDelegate().setSupportActionBar(toolbar);
-    }
-
-    @Override
-    public MenuInflater getMenuInflater() {
-        return getDelegate().getMenuInflater();
-    }
-
-    @Override
-    public void setContentView(@LayoutRes int layoutResID) {
-        getDelegate().setContentView(layoutResID);
-    }
-
-    @Override
-    public void setContentView(View view) {
-        getDelegate().setContentView(view);
-    }
-
-    @Override
-    public void setContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().setContentView(view, params);
-    }
-
-    @Override
-    public void addContentView(View view, ViewGroup.LayoutParams params) {
-        getDelegate().addContentView(view, params);
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        getDelegate().onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        getDelegate().onConfigurationChanged(newConfig);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        getDelegate().onStop();
-    }
-
-    @Override
-    protected void onPostResume() {
-        super.onPostResume();
-        getDelegate().onPostResume();
-    }
-
-    @Override
-    public View onCreatePanelView(int featureId) {
-        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
-            return getDelegate().onCreatePanelView(featureId);
-        } else {
-            return super.onCreatePanelView(featureId);
-        }
-    }
-
-    @Override
-    public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
-        if (super.onMenuItemSelected(featureId, item)) {
-            return true;
-        }
-
-        final ActionBar ab = getSupportActionBar();
-        if (item.getItemId() == android.R.id.home && ab != null &&
-                (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-            return onSupportNavigateUp();
-        }
-        return false;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        getDelegate().destroy();
-    }
-
-    @Override
-    protected void onTitleChanged(CharSequence title, int color) {
-        super.onTitleChanged(title, color);
-        getDelegate().onTitleChanged(title);
-    }
-
-    /**
-     * Enable extended support library window features.
-     * <p>
-     * This is a convenience for calling
-     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
-     * </p>
-     *
-     * @param featureId The desired feature as defined in
-     * {@link android.view.Window} or {@link WindowCompat}.
-     * @return Returns true if the requested feature is supported and now enabled.
-     *
-     * @see android.app.Activity#requestWindowFeature
-     * @see android.view.Window#requestFeature
-     */
-    public boolean supportRequestWindowFeature(int featureId) {
-        return getDelegate().supportRequestWindowFeature(featureId);
-    }
-
-    @Override
-    public void supportInvalidateOptionsMenu() {
-        getDelegate().supportInvalidateOptionsMenu();
-    }
-
-    /**
-     * @hide
-     */
-    public void invalidateOptionsMenu() {
-        getDelegate().supportInvalidateOptionsMenu();
-    }
-
-    /**
-     * Notifies the Activity that a support action mode has been started.
-     * Activity subclasses overriding this method should call the superclass implementation.
-     *
-     * @param mode The new action mode.
-     */
-    public void onSupportActionModeStarted(ActionMode mode) {
-    }
-
-    /**
-     * Notifies the activity that a support action mode has finished.
-     * Activity subclasses overriding this method should call the superclass implementation.
-     *
-     * @param mode The action mode that just finished.
-     */
-    public void onSupportActionModeFinished(ActionMode mode) {
-    }
-
-    public ActionMode startSupportActionMode(ActionMode.Callback callback) {
-        return getDelegate().startSupportActionMode(callback);
-    }
-
-    @Override
-    public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        return getDelegate().onCreatePanelMenu(featureId, menu);
-    }
-
-    @Override
-    public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        return getDelegate().onPreparePanel(featureId, view, menu);
-    }
-
-    @Override
-    public void onPanelClosed(int featureId, Menu menu) {
-        getDelegate().onPanelClosed(featureId, menu);
-    }
-
-    @Override
-    public boolean onMenuOpened(int featureId, Menu menu) {
-        return getDelegate().onMenuOpened(featureId, menu);
-    }
-
-    /**
-     * @hide
-     */
-    @Override
-    protected boolean onPrepareOptionsPanel(View view, Menu menu) {
-        return getDelegate().onPrepareOptionsPanel(view, menu);
-    }
-
-    void superSetContentView(int resId) {
-        super.setContentView(resId);
-    }
-
-    void superSetContentView(View v) {
-        super.setContentView(v);
-    }
-
-    void superSetContentView(View v, ViewGroup.LayoutParams lp) {
-        super.setContentView(v, lp);
-    }
-
-    void superAddContentView(View v, ViewGroup.LayoutParams lp) {
-        super.addContentView(v, lp);
-    }
-
-    boolean superOnCreatePanelMenu(int featureId, android.view.Menu frameworkMenu) {
-        return super.onCreatePanelMenu(featureId, frameworkMenu);
-    }
-
-    boolean superOnPreparePanel(int featureId, View view, android.view.Menu menu) {
-        return super.onPreparePanel(featureId, view, menu);
-    }
-
-    boolean superOnPrepareOptionsPanel(View view, Menu menu) {
-        return super.onPrepareOptionsPanel(view, menu);
-    }
-
-    void superOnPanelClosed(int featureId, Menu menu) {
-        super.onPanelClosed(featureId, menu);
-    }
-
-    boolean superOnMenuOpened(int featureId, Menu menu) {
-        return super.onMenuOpened(featureId, menu);
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (!getDelegate().onBackPressed()) {
-            super.onBackPressed();
-        }
-    }
-
-    /**
-     * Support library version of {@link Activity#setProgressBarVisibility(boolean)}
-     * <p>
-     * Sets the visibility of the progress bar in the title.
-     * <p>
-     * In order for the progress bar to be shown, the feature must be requested
-     * via {@link #supportRequestWindowFeature(int)}.
-     *
-     * @param visible Whether to show the progress bars in the title.
-     */
-    public void setSupportProgressBarVisibility(boolean visible) {
-        getDelegate().setSupportProgressBarVisibility(visible);
-    }
-
-    /**
-     * Support library version of {@link Activity#setProgressBarIndeterminateVisibility(boolean)}
-     * <p>
-     * Sets the visibility of the indeterminate progress bar in the title.
-     * <p>
-     * In order for the progress bar to be shown, the feature must be requested
-     * via {@link #supportRequestWindowFeature(int)}.
-     *
-     * @param visible Whether to show the progress bars in the title.
-     */
-    public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
-        getDelegate().setSupportProgressBarIndeterminateVisibility(visible);
-    }
-
-    /**
-     * Support library version of {@link Activity#setProgressBarIndeterminate(boolean)}
-     * <p>
-     * Sets whether the horizontal progress bar in the title should be indeterminate (the
-     * circular is always indeterminate).
-     * <p>
-     * In order for the progress bar to be shown, the feature must be requested
-     * via {@link #supportRequestWindowFeature(int)}.
-     *
-     * @param indeterminate Whether the horizontal progress bar should be indeterminate.
-     */
-    public void setSupportProgressBarIndeterminate(boolean indeterminate) {
-        getDelegate().setSupportProgressBarIndeterminate(indeterminate);
-    }
-
-    /**
-     * Support library version of {@link Activity#setProgress(int)}.
-     * <p>
-     * Sets the progress for the progress bars in the title.
-     * <p>
-     * In order for the progress bar to be shown, the feature must be requested
-     * via {@link #supportRequestWindowFeature(int)}.
-     *
-     * @param progress The progress for the progress bar. Valid ranges are from
-     *            0 to 10000 (both inclusive). If 10000 is given, the progress
-     *            bar will be completely filled and will fade out.
-     */
-    public void setSupportProgress(int progress) {
-        getDelegate().setSupportProgress(progress);
-    }
-
-    /**
-     * Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}.
-     * This method will be called on all platform versions.
-     *
-     * Define the synthetic task stack that will be generated during Up navigation from
-     * a different task.
-     *
-     * <p>The default implementation of this method adds the parent chain of this activity
-     * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications
-     * may choose to override this method to construct the desired task stack in a different
-     * way.</p>
-     *
-     * <p>This method will be invoked by the default implementation of {@link #onNavigateUp()}
-     * if {@link #shouldUpRecreateTask(Intent)} returns true when supplied with the intent
-     * returned by {@link #getParentActivityIntent()}.</p>
-     *
-     * <p>Applications that wish to supply extra Intent parameters to the parent stack defined
-     * by the manifest should override
-     * {@link #onPrepareSupportNavigateUpTaskStack(TaskStackBuilder)}.</p>
-     *
-     * @param builder An empty TaskStackBuilder - the application should add intents representing
-     *                the desired task stack
-     */
-    public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) {
-        builder.addParentStack(this);
-    }
-
-    /**
-     * Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}.
-     * This method will be called on all platform versions.
-     *
-     * Prepare the synthetic task stack that will be generated during Up navigation
-     * from a different task.
-     *
-     * <p>This method receives the {@link TaskStackBuilder} with the constructed series of
-     * Intents as generated by {@link #onCreateSupportNavigateUpTaskStack(TaskStackBuilder)}.
-     * If any extra data should be added to these intents before launching the new task,
-     * the application should override this method and add that data here.</p>
-     *
-     * @param builder A TaskStackBuilder that has been populated with Intents by
-     *                onCreateNavigateUpTaskStack.
-     */
-    public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) {
-    }
-
-    /**
-     * This method is called whenever the user chooses to navigate Up within your application's
-     * activity hierarchy from the action bar.
-     *
-     * <p>If a parent was specified in the manifest for this activity or an activity-alias to it,
-     * default Up navigation will be handled automatically. See
-     * {@link #getSupportParentActivityIntent()} for how to specify the parent. If any activity
-     * along the parent chain requires extra Intent arguments, the Activity subclass
-     * should override the method {@link #onPrepareSupportNavigateUpTaskStack(TaskStackBuilder)}
-     * to supply those arguments.</p>
-     *
-     * <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and
-     * Back Stack</a> from the developer guide and
-     * <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> from the design guide
-     * for more information about navigating within your app.</p>
-     *
-     * <p>See the {@link TaskStackBuilder} class and the Activity methods
-     * {@link #getSupportParentActivityIntent()}, {@link #supportShouldUpRecreateTask(Intent)}, and
-     * {@link #supportNavigateUpTo(Intent)} for help implementing custom Up navigation.</p>
-     *
-     * @return true if Up navigation completed successfully and this Activity was finished,
-     *         false otherwise.
-     */
-    public boolean onSupportNavigateUp() {
-        Intent upIntent = getSupportParentActivityIntent();
-
-        if (upIntent != null) {
-            if (supportShouldUpRecreateTask(upIntent)) {
-                TaskStackBuilder b = TaskStackBuilder.create(this);
-                onCreateSupportNavigateUpTaskStack(b);
-                onPrepareSupportNavigateUpTaskStack(b);
-                b.startActivities();
-
-                try {
-                    ActivityCompat.finishAffinity(this);
-                } catch (IllegalStateException e) {
-                    // This can only happen on 4.1+, when we don't have a parent or a result set.
-                    // In that case we should just finish().
-                    finish();
-                }
-            } else {
-                // This activity is part of the application's task, so simply
-                // navigate up to the hierarchical parent activity.
-                supportNavigateUpTo(upIntent);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Obtain an {@link Intent} that will launch an explicit target activity
-     * specified by sourceActivity's {@link NavUtils#PARENT_ACTIVITY} &lt;meta-data&gt;
-     * element in the application's manifest. If the device is running
-     * Jellybean or newer, the android:parentActivityName attribute will be preferred
-     * if it is present.
-     *
-     * @return a new Intent targeting the defined parent activity of sourceActivity
-     */
-    public Intent getSupportParentActivityIntent() {
-        return NavUtils.getParentActivityIntent(this);
-    }
-
-    /**
-     * Returns true if sourceActivity should recreate the task when navigating 'up'
-     * by using targetIntent.
-     *
-     * <p>If this method returns false the app can trivially call
-     * {@link #supportNavigateUpTo(Intent)} using the same parameters to correctly perform
-     * up navigation. If this method returns false, the app should synthesize a new task stack
-     * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
-     *
-     * @param targetIntent An intent representing the target destination for up navigation
-     * @return true if navigating up should recreate a new task stack, false if the same task
-     *         should be used for the destination
-     */
-    public boolean supportShouldUpRecreateTask(Intent targetIntent) {
-        return NavUtils.shouldUpRecreateTask(this, targetIntent);
-    }
-
-    /**
-     * Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity
-     * in the process. upIntent will have the flag {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set
-     * by this method, along with any others required for proper up navigation as outlined
-     * in the Android Design Guide.
-     *
-     * <p>This method should be used when performing up navigation from within the same task
-     * as the destination. If up navigation should cross tasks in some cases, see
-     * {@link #supportShouldUpRecreateTask(Intent)}.</p>
-     *
-     * @param upIntent An intent representing the target destination for up navigation
-     */
-    public void supportNavigateUpTo(Intent upIntent) {
-        NavUtils.navigateUpTo(this, upIntent);
-    }
-
-    @Override
-    public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
-        return getDelegate().getDrawerToggleDelegate();
-    }
-
-    @Nullable
-    @Override
-    /**
-     * Temporary method until ActionBarDrawerToggle transition from v4 to v7 is complete.
-     */
-    public android.support.v7.app.ActionBarDrawerToggle.Delegate getV7DrawerToggleDelegate() {
-        return getDelegate().getV7DrawerToggleDelegate();
-    }
-
-    @Override
-    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
-        return getDelegate().onKeyShortcut(keyCode, event);
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        // First let the Activity try and handle it (for back, etc)
-        if (super.onKeyDown(keyCode, event)) {
-            return true;
-        }
-        return getDelegate().onKeyDown(keyCode, event);
-    }
-
-    /**
-     * Use {@link #onSupportContentChanged()} instead.
-     */
-    public final void onContentChanged() {
-        getDelegate().onContentChanged();
-    }
-
-    /**
-     * This hook is called whenever the content view of the screen changes.
-     * @see android.app.Activity#onContentChanged()
-     */
-    public void onSupportContentChanged() {
-    }
-
-    @Override
-    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
-        // Allow super (FragmentActivity) to try and create a view first
-        final View result = super.onCreateView(name, context, attrs);
-        if (result != null) {
-            return result;
-        }
-        // If we reach here super didn't create a View, so let our delegate attempt it
-        return getDelegate().createView(name, attrs);
-    }
-
-    private ActionBarActivityDelegate getDelegate() {
-        if (mDelegate == null) {
-            mDelegate = ActionBarActivityDelegate.createDelegate(this);
-        }
-        return mDelegate;
-    }
+@Deprecated
+public class ActionBarActivity extends AppCompatActivity {
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
deleted file mode 100644
index 855f9e6..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActionBarDrawerToggle;
-import android.support.v7.appcompat.R;
-import android.support.v7.internal.app.WindowCallback;
-import android.support.v7.internal.view.SupportMenuInflater;
-import android.support.v7.view.ActionMode;
-import android.support.v7.widget.Toolbar;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-abstract class ActionBarActivityDelegate {
-
-    static final String METADATA_UI_OPTIONS = "android.support.UI_OPTIONS";
-
-    private static final String TAG = "ActionBarActivityDelegate";
-
-    static ActionBarActivityDelegate createDelegate(ActionBarActivity activity) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            return new ActionBarActivityDelegateHC(activity);
-        } else {
-            return new ActionBarActivityDelegateBase(activity);
-        }
-    }
-
-    final ActionBarActivity mActivity;
-
-    private ActionBar mActionBar;
-    private MenuInflater mMenuInflater;
-
-    // true if this activity has an action bar.
-    boolean mHasActionBar;
-    // true if this activity's action bar overlays other activity content.
-    boolean mOverlayActionBar;
-    // true if this any action modes should overlay the activity content
-    boolean mOverlayActionMode;
-    // true if this activity is floating (e.g. Dialog)
-    boolean mIsFloating;
-    // The default window callback
-    final WindowCallback mDefaultWindowCallback = new WindowCallback() {
-        @Override
-        public boolean onMenuItemSelected(int featureId, MenuItem menuItem) {
-            return mActivity.onMenuItemSelected(featureId, menuItem);
-        }
-
-        @Override
-        public boolean onCreatePanelMenu(int featureId, Menu menu) {
-            return mActivity.superOnCreatePanelMenu(featureId, menu);
-        }
-
-        @Override
-        public boolean onPreparePanel(int featureId, View menuView, Menu menu) {
-            return mActivity.superOnPreparePanel(featureId, menuView, menu);
-        }
-
-        @Override
-        public void onPanelClosed(int featureId, Menu menu) {
-            mActivity.onPanelClosed(featureId, menu);
-        }
-
-        @Override
-        public boolean onMenuOpened(int featureId, Menu menu) {
-            return mActivity.onMenuOpened(featureId, menu);
-        }
-
-        @Override
-        public ActionMode startActionMode(ActionMode.Callback callback) {
-            return startSupportActionModeFromWindow(callback);
-        }
-
-        @Override
-        public View onCreatePanelView(int featureId) {
-            return null;
-        }
-    };
-    // The fake window callback we're currently using
-    private WindowCallback mWindowCallback;
-    private boolean mIsDestroyed;
-
-    ActionBarActivityDelegate(ActionBarActivity activity) {
-        mActivity = activity;
-        mWindowCallback = mDefaultWindowCallback;
-    }
-
-    abstract ActionBar createSupportActionBar();
-
-    final ActionBar getSupportActionBar() {
-        // The Action Bar should be lazily created as hasActionBar
-        // could change after onCreate
-        if (mHasActionBar) {
-            if (mActionBar == null) {
-                mActionBar = createSupportActionBar();
-            }
-        }
-        return mActionBar;
-    }
-
-    protected final void setSupportActionBar(ActionBar actionBar) {
-        mActionBar = actionBar;
-    }
-
-    abstract void setSupportActionBar(Toolbar toolbar);
-
-    MenuInflater getMenuInflater() {
-        if (mMenuInflater == null) {
-            mMenuInflater = new SupportMenuInflater(getActionBarThemedContext());
-        }
-        return mMenuInflater;
-    }
-
-    void onCreate(Bundle savedInstanceState) {
-        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme);
-
-        if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
-            a.recycle();
-            throw new IllegalStateException(
-                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
-        }
-
-        mHasActionBar = a.getBoolean(R.styleable.Theme_windowActionBar, false);
-        mOverlayActionBar = a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false);
-        mOverlayActionMode = a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false);
-        mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
-        a.recycle();
-    }
-
-    abstract void onConfigurationChanged(Configuration newConfig);
-
-    abstract void onStop();
-
-    abstract void onPostResume();
-
-    abstract void setContentView(View v);
-
-    abstract void setContentView(int resId);
-
-    abstract void setContentView(View v, ViewGroup.LayoutParams lp);
-
-    abstract void addContentView(View v, ViewGroup.LayoutParams lp);
-
-    abstract void onTitleChanged(CharSequence title);
-
-    abstract void supportInvalidateOptionsMenu();
-
-    abstract boolean supportRequestWindowFeature(int featureId);
-
-    // Methods used to create and respond to options menu
-    abstract View onCreatePanelView(int featureId);
-
-    abstract boolean onPreparePanel(int featureId, View view, Menu menu);
-
-    abstract void onPanelClosed(int featureId, Menu menu);
-
-    abstract boolean onMenuOpened(int featureId, Menu menu);
-
-    boolean onPrepareOptionsPanel(View view, Menu menu) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
-            // Call straight through to onPrepareOptionsMenu, bypassing super.onPreparePanel().
-            // This is because Activity.onPreparePanel() on <v4.1 calls menu.hasVisibleItems(),
-            // which interferes with the initially invisible items.
-            return mActivity.onPrepareOptionsMenu(menu);
-        }
-        return mActivity.superOnPrepareOptionsPanel(view, menu);
-    }
-
-    abstract boolean onCreatePanelMenu(int featureId, Menu menu);
-
-    abstract boolean onBackPressed();
-
-    abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
-
-    abstract void setSupportProgressBarVisibility(boolean visible);
-
-    abstract void setSupportProgressBarIndeterminateVisibility(boolean visible);
-
-    abstract void setSupportProgressBarIndeterminate(boolean indeterminate);
-
-    abstract void setSupportProgress(int progress);
-
-    boolean onKeyDown(int keyCode, KeyEvent event) {
-        return false;
-    }
-
-    abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
-
-    final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
-        return new ActionBarDrawableToggleImpl();
-    }
-
-    final android.support.v7.app.ActionBarDrawerToggle.Delegate getV7DrawerToggleDelegate() {
-        return new ActionBarDrawableToggleImpl();
-    }
-
-    abstract int getHomeAsUpIndicatorAttrId();
-
-    abstract void onContentChanged();
-
-    final String getUiOptionsFromMetadata() {
-        try {
-            PackageManager pm = mActivity.getPackageManager();
-            ActivityInfo info = pm.getActivityInfo(mActivity.getComponentName(),
-                    PackageManager.GET_META_DATA);
-
-            String uiOptions = null;
-            if (info.metaData != null) {
-                uiOptions = info.metaData.getString(METADATA_UI_OPTIONS);
-            }
-            return uiOptions;
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "getUiOptionsFromMetadata: Activity '" + mActivity.getClass()
-                    .getSimpleName() + "' not in manifest");
-            return null;
-        }
-    }
-
-    protected final Context getActionBarThemedContext() {
-        Context context = null;
-
-        // If we have an action bar, let it return a themed context
-        ActionBar ab = getSupportActionBar();
-        if (ab != null) {
-            context = ab.getThemedContext();
-        }
-
-        if (context == null) {
-            context = mActivity;
-        }
-        return context;
-    }
-
-    abstract View createView(String name, @NonNull AttributeSet attrs);
-
-
-    private class ActionBarDrawableToggleImpl implements
-            android.support.v7.app.ActionBarDrawerToggle.Delegate,
-            ActionBarDrawerToggle.Delegate {
-        @Override
-        public Drawable getThemeUpIndicator() {
-            final TypedArray a = ActionBarActivityDelegate.this.getActionBarThemedContext()
-                    .obtainStyledAttributes(new int[]{ getHomeAsUpIndicatorAttrId() });
-            final Drawable result = a.getDrawable(0);
-            a.recycle();
-            return result;
-        }
-
-        @Override
-        public Context getActionBarThemedContext() {
-            return ActionBarActivityDelegate.this.getActionBarThemedContext();
-        }
-
-        @Override
-        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.setHomeAsUpIndicator(upDrawable);
-                ab.setHomeActionContentDescription(contentDescRes);
-            }
-        }
-
-        @Override
-        public void setActionBarDescription(int contentDescRes) {
-            ActionBar ab = getSupportActionBar();
-            if (ab != null) {
-                ab.setHomeActionContentDescription(contentDescRes);
-            }
-        }
-    }
-
-    abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
-
-    final void setWindowCallback(WindowCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback can not be null");
-        }
-        mWindowCallback = callback;
-    }
-
-    final WindowCallback getWindowCallback() {
-        return mWindowCallback;
-    }
-
-    final void destroy() {
-        mIsDestroyed = true;
-    }
-
-    final boolean isDestroyed() {
-        return mIsDestroyed;
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java b/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
index 7edab96..93a79a6 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
@@ -28,6 +28,7 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.widget.Toolbar;
+import android.util.Log;
 import android.view.MenuItem;
 import android.view.View;
 import android.support.v7.appcompat.R;
@@ -77,15 +78,6 @@
         Delegate getDrawerToggleDelegate();
     }
 
-    /**
-     * @deprecated Temporary class for the transition from old drawer toggle to new drawer toggle.
-     */
-    interface TmpDelegateProvider {
-
-        @Nullable
-        Delegate getV7DrawerToggleDelegate();
-    }
-
     public interface Delegate {
 
         /**
@@ -112,6 +104,12 @@
          * Returns the context of ActionBar
          */
         Context getActionBarThemedContext();
+
+        /**
+         * Returns whether navigation icon is visible or not.
+         * Used to print warning messages in case developer forgets to set displayHomeAsUp to true
+         */
+        boolean isNavigationVisible();
     }
 
     private final Delegate mActivityImpl;
@@ -125,6 +123,9 @@
     private final int mCloseDrawerContentDescRes;
     // used in toolbar mode when DrawerToggle is disabled
     private View.OnClickListener mToolbarNavigationClickListener;
+    // If developer does not set displayHomeAsUp, DrawerToggle won't show up.
+    // DrawerToggle logs a warning if this case is detected
+    private boolean mWarnedForDisplayHomeAsUp = false;
 
     /**
      * Construct a new ActionBarDrawerToggle.
@@ -205,8 +206,6 @@
             });
         } else if (activity instanceof DelegateProvider) { // Allow the Activity to provide an impl
             mActivityImpl = ((DelegateProvider) activity).getDrawerToggleDelegate();
-        } else if (activity instanceof TmpDelegateProvider) {// tmp interface for transition
-            mActivityImpl = ((TmpDelegateProvider) activity).getV7DrawerToggleDelegate();
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
             mActivityImpl = new JellybeanMr2Delegate(activity);
         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
@@ -449,6 +448,12 @@
     }
 
     void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
+        if (!mWarnedForDisplayHomeAsUp && !mActivityImpl.isNavigationVisible()) {
+            Log.w("ActionBarDrawerToggle", "DrawerToggle may not show up because NavigationIcon"
+                    + " is not visible. You may need to call "
+                    + "actionbar.setDisplayHomeAsUpEnabled(true);");
+            mWarnedForDisplayHomeAsUp = true;
+        }
         mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes);
     }
 
@@ -530,6 +535,13 @@
         }
 
         @Override
+        public boolean isNavigationVisible() {
+            final ActionBar actionBar = mActivity.getActionBar();
+            return actionBar != null
+                    && (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+        }
+
+        @Override
         public void setActionBarUpIndicator(Drawable themeImage, int contentDescRes) {
             mActivity.getActionBar().setDisplayShowHomeEnabled(true);
             mSetIndicatorInfo = ActionBarDrawerToggleHoneycomb.setActionBarUpIndicator(
@@ -577,6 +589,13 @@
         }
 
         @Override
+        public boolean isNavigationVisible() {
+            final ActionBar actionBar = mActivity.getActionBar();
+            return actionBar != null &&
+                    (actionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+        }
+
+        @Override
         public void setActionBarUpIndicator(Drawable drawable, int contentDescRes) {
             final ActionBar actionBar = mActivity.getActionBar();
             if (actionBar != null) {
@@ -600,35 +619,44 @@
     static class ToolbarCompatDelegate implements Delegate {
 
         final Toolbar mToolbar;
+        final Drawable mDefaultUpIndicator;
+        final CharSequence mDefaultContentDescription;
 
         ToolbarCompatDelegate(Toolbar toolbar) {
             mToolbar = toolbar;
+            mDefaultUpIndicator = toolbar.getNavigationIcon();
+            mDefaultContentDescription = toolbar.getNavigationContentDescription();
         }
 
         @Override
         public void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes) {
             mToolbar.setNavigationIcon(upDrawable);
-            mToolbar.setNavigationContentDescription(contentDescRes);
+            setActionBarDescription(contentDescRes);
         }
 
         @Override
         public void setActionBarDescription(@StringRes int contentDescRes) {
-            mToolbar.setNavigationContentDescription(contentDescRes);
+            if (contentDescRes == 0) {
+                mToolbar.setNavigationContentDescription(mDefaultContentDescription);
+            } else {
+                mToolbar.setNavigationContentDescription(contentDescRes);
+            }
         }
 
         @Override
         public Drawable getThemeUpIndicator() {
-            final TypedArray a = mToolbar.getContext()
-                    .obtainStyledAttributes(new int[]{android.R.id.home});
-            final Drawable result = a.getDrawable(0);
-            a.recycle();
-            return result;
+            return mDefaultUpIndicator;
         }
 
         @Override
         public Context getActionBarThemedContext() {
             return mToolbar.getContext();
         }
+
+        @Override
+        public boolean isNavigationVisible() {
+            return true;
+        }
     }
 
     /**
@@ -660,5 +688,10 @@
         public Context getActionBarThemedContext() {
             return mActivity;
         }
+
+        @Override
+        public boolean isNavigationVisible() {
+            return true;
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
new file mode 100644
index 0000000..5be3d3d
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -0,0 +1,416 @@
+/*
+ * 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.v7.app;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.NavUtils;
+import android.support.v4.app.TaskStackBuilder;
+import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class for activities that use the
+ * <a href="{@docRoot}tools/extras/support-library.html">support library</a> action bar features.
+ *
+ * <p>You can add an {@link android.support.v7.app.ActionBar} to your activity when running on API level 7 or higher
+ * by extending this class for your activity and setting the activity theme to
+ * {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ *
+ * <p>For information about how to use the action bar, including how to add action items, navigation
+ * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
+ * Bar</a> API guide.</p>
+ * </div>
+ */
+public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
+        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
+
+    private AppCompatDelegate mDelegate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        super.onCreate(savedInstanceState);
+        getDelegate().onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    /**
+     * Support library version of {@link android.app.Activity#getActionBar}.
+     *
+     * <p>Retrieve a reference to this activity's ActionBar.
+     *
+     * @return The Activity's ActionBar, or null if it does not have one.
+     */
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    /**
+     * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this
+     * Activity window.
+     *
+     * <p>When set to a non-null value the {@link #getActionBar()} method will return
+     * an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were
+     * a traditional window decor action bar. The toolbar's menu will be populated with the
+     * Activity's options menu and the navigation button will be wired through the standard
+     * {@link android.R.id#home home} menu select action.</p>
+     *
+     * <p>In order to use a Toolbar within the Activity's window content the application
+     * must not request the window feature {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+     *
+     * @param toolbar Toolbar to set as the Activity's action bar
+     */
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+
+    @Override
+    public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
+        if (super.onMenuItemSelected(featureId, item)) {
+            return true;
+        }
+
+        final ActionBar ab = getSupportActionBar();
+        if (item.getItemId() == android.R.id.home && ab != null &&
+                (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+            return onSupportNavigateUp();
+        }
+        return false;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+
+    @Override
+    protected void onTitleChanged(CharSequence title, int color) {
+        super.onTitleChanged(title, color);
+        getDelegate().setTitle(title);
+    }
+
+    /**
+     * Enable extended support library window features.
+     * <p>
+     * This is a convenience for calling
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     * </p>
+     *
+     * @param featureId The desired feature as defined in
+     * {@link android.view.Window} or {@link android.support.v4.view.WindowCompat}.
+     * @return Returns true if the requested feature is supported and now enabled.
+     *
+     * @see android.app.Activity#requestWindowFeature
+     * @see android.view.Window#requestFeature
+     */
+    public boolean supportRequestWindowFeature(int featureId) {
+        return getDelegate().requestWindowFeature(featureId);
+    }
+
+    @Override
+    public void supportInvalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    /**
+     * @hide
+     */
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    /**
+     * Notifies the Activity that a support action mode has been started.
+     * Activity subclasses overriding this method should call the superclass implementation.
+     *
+     * @param mode The new action mode.
+     */
+    public void onSupportActionModeStarted(ActionMode mode) {
+    }
+
+    /**
+     * Notifies the activity that a support action mode has finished.
+     * Activity subclasses overriding this method should call the superclass implementation.
+     *
+     * @param mode The action mode that just finished.
+     */
+    public void onSupportActionModeFinished(ActionMode mode) {
+    }
+
+    public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+        return getDelegate().startSupportActionMode(callback);
+    }
+
+    /**
+     * @deprecated Progress bars are no longer provided in AppCompat.
+     */
+    @Deprecated
+    public void setSupportProgressBarVisibility(boolean visible) {
+    }
+
+    /**
+     * @deprecated Progress bars are no longer provided in AppCompat.
+     */
+    @Deprecated
+    public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
+    }
+
+    /**
+     * @deprecated Progress bars are no longer provided in AppCompat.
+     */
+    @Deprecated
+    public void setSupportProgressBarIndeterminate(boolean indeterminate) {
+    }
+
+    /**
+     * @deprecated Progress bars are no longer provided in AppCompat.
+     */
+    @Deprecated
+    public void setSupportProgress(int progress) {
+    }
+
+    /**
+     * Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}.
+     * This method will be called on all platform versions.
+     *
+     * Define the synthetic task stack that will be generated during Up navigation from
+     * a different task.
+     *
+     * <p>The default implementation of this method adds the parent chain of this activity
+     * as specified in the manifest to the supplied {@link android.support.v4.app.TaskStackBuilder}. Applications
+     * may choose to override this method to construct the desired task stack in a different
+     * way.</p>
+     *
+     * <p>This method will be invoked by the default implementation of {@link #onNavigateUp()}
+     * if {@link #shouldUpRecreateTask(android.content.Intent)} returns true when supplied with the intent
+     * returned by {@link #getParentActivityIntent()}.</p>
+     *
+     * <p>Applications that wish to supply extra Intent parameters to the parent stack defined
+     * by the manifest should override
+     * {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.</p>
+     *
+     * @param builder An empty TaskStackBuilder - the application should add intents representing
+     *                the desired task stack
+     */
+    public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+        builder.addParentStack(this);
+    }
+
+    /**
+     * Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}.
+     * This method will be called on all platform versions.
+     *
+     * Prepare the synthetic task stack that will be generated during Up navigation
+     * from a different task.
+     *
+     * <p>This method receives the {@link android.support.v4.app.TaskStackBuilder} with the constructed series of
+     * Intents as generated by {@link #onCreateSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.
+     * If any extra data should be added to these intents before launching the new task,
+     * the application should override this method and add that data here.</p>
+     *
+     * @param builder A TaskStackBuilder that has been populated with Intents by
+     *                onCreateNavigateUpTaskStack.
+     */
+    public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+    }
+
+    /**
+     * This method is called whenever the user chooses to navigate Up within your application's
+     * activity hierarchy from the action bar.
+     *
+     * <p>If a parent was specified in the manifest for this activity or an activity-alias to it,
+     * default Up navigation will be handled automatically. See
+     * {@link #getSupportParentActivityIntent()} for how to specify the parent. If any activity
+     * along the parent chain requires extra Intent arguments, the Activity subclass
+     * should override the method {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}
+     * to supply those arguments.</p>
+     *
+     * <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and
+     * Back Stack</a> from the developer guide and
+     * <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> from the design guide
+     * for more information about navigating within your app.</p>
+     *
+     * <p>See the {@link android.support.v4.app.TaskStackBuilder} class and the Activity methods
+     * {@link #getSupportParentActivityIntent()}, {@link #supportShouldUpRecreateTask(android.content.Intent)}, and
+     * {@link #supportNavigateUpTo(android.content.Intent)} for help implementing custom Up navigation.</p>
+     *
+     * @return true if Up navigation completed successfully and this Activity was finished,
+     *         false otherwise.
+     */
+    public boolean onSupportNavigateUp() {
+        Intent upIntent = getSupportParentActivityIntent();
+
+        if (upIntent != null) {
+            if (supportShouldUpRecreateTask(upIntent)) {
+                TaskStackBuilder b = TaskStackBuilder.create(this);
+                onCreateSupportNavigateUpTaskStack(b);
+                onPrepareSupportNavigateUpTaskStack(b);
+                b.startActivities();
+
+                try {
+                    ActivityCompat.finishAffinity(this);
+                } catch (IllegalStateException e) {
+                    // This can only happen on 4.1+, when we don't have a parent or a result set.
+                    // In that case we should just finish().
+                    finish();
+                }
+            } else {
+                // This activity is part of the application's task, so simply
+                // navigate up to the hierarchical parent activity.
+                supportNavigateUpTo(upIntent);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Obtain an {@link android.content.Intent} that will launch an explicit target activity
+     * specified by sourceActivity's {@link android.support.v4.app.NavUtils#PARENT_ACTIVITY} &lt;meta-data&gt;
+     * element in the application's manifest. If the device is running
+     * Jellybean or newer, the android:parentActivityName attribute will be preferred
+     * if it is present.
+     *
+     * @return a new Intent targeting the defined parent activity of sourceActivity
+     */
+    public Intent getSupportParentActivityIntent() {
+        return NavUtils.getParentActivityIntent(this);
+    }
+
+    /**
+     * Returns true if sourceActivity should recreate the task when navigating 'up'
+     * by using targetIntent.
+     *
+     * <p>If this method returns false the app can trivially call
+     * {@link #supportNavigateUpTo(android.content.Intent)} using the same parameters to correctly perform
+     * up navigation. If this method returns false, the app should synthesize a new task stack
+     * by using {@link android.support.v4.app.TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
+     *
+     * @param targetIntent An intent representing the target destination for up navigation
+     * @return true if navigating up should recreate a new task stack, false if the same task
+     *         should be used for the destination
+     */
+    public boolean supportShouldUpRecreateTask(Intent targetIntent) {
+        return NavUtils.shouldUpRecreateTask(this, targetIntent);
+    }
+
+    /**
+     * Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity
+     * in the process. upIntent will have the flag {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} set
+     * by this method, along with any others required for proper up navigation as outlined
+     * in the Android Design Guide.
+     *
+     * <p>This method should be used when performing up navigation from within the same task
+     * as the destination. If up navigation should cross tasks in some cases, see
+     * {@link #supportShouldUpRecreateTask(android.content.Intent)}.</p>
+     *
+     * @param upIntent An intent representing the target destination for up navigation
+     */
+    public void supportNavigateUpTo(Intent upIntent) {
+        NavUtils.navigateUpTo(this, upIntent);
+    }
+
+    @Override
+    public void onContentChanged() {
+        // Call onSupportContentChanged() for legacy reasons
+        onSupportContentChanged();
+    }
+
+    /**
+     * @deprecated Use {@link #onContentChanged()} instead.
+     */
+    @Deprecated
+    public void onSupportContentChanged() {
+    }
+
+    @Nullable
+    @Override
+    public ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
+        return getDelegate().getDrawerToggleDelegate();
+    }
+
+    /**
+     * @return The {@link AppCompatDelegate} being used by this Activity.
+     */
+    public AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, this);
+        }
+        return mDelegate;
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatCallback.java b/v7/appcompat/src/android/support/v7/app/AppCompatCallback.java
new file mode 100644
index 0000000..dba77a2
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatCallback.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.support.v7.view.ActionMode;
+
+/**
+ * Implemented this in order for AppCompat to be able to callback in certain situations.
+ * <p>
+ * This should be provided to
+ * {@link AppCompatDelegate#create(android.app.Activity, AppCompatCallback)}.
+ */
+public interface AppCompatCallback {
+
+    /**
+     * Called when a support action mode has been started.
+     *
+     * @param mode The new action mode.
+     */
+    void onSupportActionModeStarted(ActionMode mode);
+
+    /**
+     * Called when a support action mode has finished.
+     *
+     * @param mode The action mode that just finished.
+     */
+    void onSupportActionModeFinished(ActionMode mode);
+
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
new file mode 100644
index 0000000..ecb1178
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
+import android.util.AttributeSet;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This class represents a delegate which you can use to extend AppCompat's support to any
+ * {@link android.app.Activity}.
+ * <p>
+ * When using an {@link AppCompatDelegate}, you should any methods exposed in it rather than the
+ * {@link android.app.Activity} method of the same name. This applies to:
+ * <ul>
+ *     <li>{@link #addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
+ *     <li>{@link #setContentView(int)}</li>
+ *     <li>{@link #setContentView(android.view.View)}</li>
+ *     <li>{@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}</li>
+ *     <li>{@link #requestWindowFeature(int)}</li>
+ *     <li>{@link #invalidateOptionsMenu()}</li>
+ *     <li>{@link #startSupportActionMode(android.support.v7.view.ActionMode.Callback)}</li>
+ *     <li>{@link #setSupportActionBar(android.support.v7.widget.Toolbar)}</li>
+ *     <li>{@link #getSupportActionBar()}</li>
+ *     <li>{@link #getMenuInflater()}</li>
+ * </ul>
+ * There also some Activity lifecycle methods which should be proxied to the delegate:
+ * <ul>
+ *     <li>{@link #onCreate(android.os.Bundle)}</li>
+ *     <li>{@link #onPostCreate(android.os.Bundle)}</li>
+ *     <li>{@link #onConfigurationChanged(android.content.res.Configuration)}</li>
+ *     <li>{@link #setTitle(CharSequence)}</li>
+ *     <li>{@link #onStop()}</li>
+ *     <li>{@link #onDestroy()}</li>
+ * </ul>
+ * <p>
+ * An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
+ * so the instance returned from {@link #create(Activity, AppCompatCallback)} should be kept
+ * until the Activity is destroyed.
+ */
+public abstract class AppCompatDelegate {
+
+    static final String TAG = "AppCompatDelegate";
+
+    /**
+     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
+     *
+     * @param callback An optional callback for AppCompat specific events
+     */
+    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            return new AppCompatDelegateImplV11(activity, activity.getWindow(), callback);
+        } else {
+            return new AppCompatDelegateImplV7(activity, activity.getWindow(), callback);
+        }
+    }
+
+    /**
+     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code dialog}.
+     *
+     * @param callback An optional callback for AppCompat specific events
+     */
+    public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            return new AppCompatDelegateImplV11(dialog.getContext(), dialog.getWindow(), callback);
+        } else {
+            return new AppCompatDelegateImplV7(dialog.getContext(), dialog.getWindow(), callback);
+        }
+    }
+
+    /**
+     * Private constructor
+     */
+    AppCompatDelegate() {}
+
+    /**
+     * Support library version of {@link Activity#getActionBar}.
+     *
+     * @return AppCompat's action bar, or null if it does not have one.
+     */
+    public abstract ActionBar getSupportActionBar();
+
+    /**
+     * Set a {@link Toolbar} to act as the {@link ActionBar} for this delegate.
+     *
+     * <p>When set to a non-null value the {@link #getSupportActionBar()} ()} method will return
+     * an {@link ActionBar} object that can be used to control the given toolbar as if it were
+     * a traditional window decor action bar. The toolbar's menu will be populated with the
+     * Activity's options menu and the navigation button will be wired through the standard
+     * {@link android.R.id#home home} menu select action.</p>
+     *
+     * <p>In order to use a Toolbar within the Activity's window content the application
+     * must not request the window feature
+     * {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+     *
+     * @param toolbar Toolbar to set as the Activity's action bar
+     */
+    public abstract void setSupportActionBar(Toolbar toolbar);
+
+    /**
+     * Return the value of this call from your {@link Activity#getMenuInflater()}
+     */
+    public abstract MenuInflater getMenuInflater();
+
+    /**
+     * Should be called from {@link Activity#onCreate Activity.onCreate()}
+     */
+    public abstract void onCreate(Bundle savedInstanceState);
+
+    /**
+     * Should be called from {@link Activity#onPostCreate(android.os.Bundle)}
+     */
+    public abstract void onPostCreate(Bundle savedInstanceState);
+
+    /**
+     * Should be called from
+     * {@link Activity#onConfigurationChanged}
+     */
+    public abstract void onConfigurationChanged(Configuration newConfig);
+
+    /**
+     * Should be called from {@link Activity#onStop Activity.onStop()}
+     */
+    public abstract void onStop();
+
+    /**
+     * Should be called from {@link Activity#onPostResume()}
+     */
+    public abstract void onPostResume();
+
+    /**
+     * Should be called instead of {@link Activity#setContentView(android.view.View)}}
+     */
+    public abstract void setContentView(View v);
+
+    /**
+     * Should be called instead of {@link Activity#setContentView(int)}}
+     */
+    public abstract void setContentView(int resId);
+
+    /**
+     * Should be called instead of
+     * {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
+     */
+    public abstract void setContentView(View v, ViewGroup.LayoutParams lp);
+
+    /**
+     * Should be called instead of
+     * {@link Activity#addContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
+     */
+    public abstract void addContentView(View v, ViewGroup.LayoutParams lp);
+
+    /**
+     * Should be called from {@link Activity#onTitleChanged(CharSequence, int)}}
+     */
+    public abstract void setTitle(CharSequence title);
+
+    /**
+     * Should be called from {@link Activity#invalidateOptionsMenu()}} or
+     * {@link FragmentActivity#supportInvalidateOptionsMenu()}.
+     */
+    public abstract void invalidateOptionsMenu();
+
+    /**
+     * Should be called from {@link Activity#onDestroy()}
+     */
+    public abstract void onDestroy();
+
+    /**
+     * Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity
+     * if it implements {@link ActionBarDrawerToggle.DelegateProvider}.
+     */
+    public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+
+    /**
+     * Enable extended window features.  This should be called instead of
+     * {@link android.app.Activity#requestWindowFeature(int)} or
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     *
+     * @param featureId The desired feature as defined in {@link android.view.Window}.
+     * @return Returns true if the requested feature is supported and now
+     *         enabled.
+     */
+    public abstract boolean requestWindowFeature(int featureId);
+
+    /**
+     * Start an action mode.
+     *
+     * @param callback Callback that will manage lifecycle events for this context mode
+     * @return The ContextMode that was started, or null if it was canceled
+     */
+    public abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
+
+    /**
+     * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
+     * the framework widgets with compatible tinted versions. This should be called before
+     * {@code super.onCreate()} as so:
+     * <pre class="prettyprint">
+     * protected void onCreate(Bundle savedInstanceState) {
+     *     getDelegate().installViewFactory();
+     *     super.onCreate(savedInstanceState);
+     *     getDelegate().onCreate(savedInstanceState);
+     *
+     *     // ...
+     * }
+     * </pre>
+     * If you are using your own {@link android.view.LayoutInflater.Factory Factory} or
+     * {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call
+     * {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)}
+     * from your factory to return any compatible widgets.
+     */
+    public abstract void installViewFactory();
+
+    /**
+     * This should be called from a
+     * {@link android.support.v4.view.LayoutInflaterFactory LayoutInflaterFactory} in order
+     * to return tint-aware widgets.
+     * <p>
+     * This is only needed if you are using your own
+     * {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
+     * installed the default factory via {@link #installViewFactory()}.
+     */
+    public abstract View createView(View parent, String name, @NonNull Context context,
+            @NonNull AttributeSet attrs);
+
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
new file mode 100644
index 0000000..ac46f3c
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.SupportMenuInflater;
+import android.support.v7.internal.view.WindowCallbackWrapper;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.widget.TintTypedArray;
+import android.support.v7.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.Window;
+
+abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
+
+    final Context mContext;
+    final Window mWindow;
+    final Window.Callback mOriginalWindowCallback;
+    final AppCompatCallback mAppCompatCallback;
+
+    private ActionBar mActionBar;
+    private MenuInflater mMenuInflater;
+
+    // true if this activity has an action bar.
+    boolean mHasActionBar;
+    // true if this activity's action bar overlays other activity content.
+    boolean mOverlayActionBar;
+    // true if this any action modes should overlay the activity content
+    boolean mOverlayActionMode;
+    // true if this activity is floating (e.g. Dialog)
+    boolean mIsFloating;
+    // true if this activity has no title
+    boolean mWindowNoTitle;
+
+    private CharSequence mTitle;
+
+    private boolean mIsDestroyed;
+
+    AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
+        mContext = context;
+        mWindow = window;
+        mAppCompatCallback = callback;
+
+        mOriginalWindowCallback = mWindow.getCallback();
+        if (mOriginalWindowCallback instanceof AppCompatWindowCallback) {
+            throw new IllegalStateException(
+                    "AppCompat has already installed itself into the Window");
+        }
+        // Now install the new callback
+        mWindow.setCallback(new AppCompatWindowCallback(mOriginalWindowCallback));
+    }
+
+    abstract ActionBar createSupportActionBar();
+
+    @Override
+    public ActionBar getSupportActionBar() {
+        // The Action Bar should be lazily created as hasActionBar
+        // could change after onCreate
+        if (mHasActionBar) {
+            if (mActionBar == null) {
+                mActionBar = createSupportActionBar();
+            }
+        }
+        return mActionBar;
+    }
+
+    final ActionBar peekSupportActionBar() {
+        return mActionBar;
+    }
+
+    final void setSupportActionBar(ActionBar actionBar) {
+        mActionBar = actionBar;
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        if (mMenuInflater == null) {
+            mMenuInflater = new SupportMenuInflater(getActionBarThemedContext());
+        }
+        return mMenuInflater;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+
+        if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
+            a.recycle();
+            throw new IllegalStateException(
+                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
+        }
+
+        if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) {
+            mHasActionBar = true;
+        }
+        if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) {
+            mOverlayActionBar = true;
+        }
+        if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) {
+            mOverlayActionMode = true;
+        }
+        mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
+        mWindowNoTitle = a.getBoolean(R.styleable.Theme_windowNoTitle, false);
+        a.recycle();
+    }
+
+    // Methods used to create and respond to options menu
+    abstract boolean onPanelClosed(int featureId, Menu menu);
+
+    abstract boolean onMenuOpened(int featureId, Menu menu);
+
+    abstract boolean dispatchKeyEvent(KeyEvent event);
+
+    abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
+
+    @Override
+    public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
+        return new ActionBarDrawableToggleImpl();
+    }
+
+    final Context getActionBarThemedContext() {
+        Context context = null;
+
+        // If we have an action bar, let it return a themed context
+        ActionBar ab = getSupportActionBar();
+        if (ab != null) {
+            context = ab.getThemedContext();
+        }
+
+        if (context == null) {
+            context = mContext;
+        }
+        return context;
+    }
+
+    private class ActionBarDrawableToggleImpl implements ActionBarDrawerToggle.Delegate {
+        @Override
+        public Drawable getThemeUpIndicator() {
+            final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+                    getActionBarThemedContext(), null, new int[]{ R.attr.homeAsUpIndicator });
+            final Drawable result = a.getDrawable(0);
+            a.recycle();
+            return result;
+        }
+
+        @Override
+        public Context getActionBarThemedContext() {
+            return AppCompatDelegateImplBase.this.getActionBarThemedContext();
+        }
+
+        @Override
+        public boolean isNavigationVisible() {
+            final ActionBar ab = getSupportActionBar();
+            return ab != null && (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+        }
+
+        @Override
+        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.setHomeAsUpIndicator(upDrawable);
+                ab.setHomeActionContentDescription(contentDescRes);
+            }
+        }
+
+        @Override
+        public void setActionBarDescription(int contentDescRes) {
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.setHomeActionContentDescription(contentDescRes);
+            }
+        }
+    }
+
+    abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
+
+    @Override
+    public final void onDestroy() {
+        mIsDestroyed = true;
+    }
+
+    final boolean isDestroyed() {
+        return mIsDestroyed;
+    }
+
+    final Window.Callback getWindowCallback() {
+        return mWindow.getCallback();
+    }
+
+    @Override
+    public final void setTitle(CharSequence title) {
+        mTitle = title;
+        onTitleChanged(title);
+    }
+
+    abstract void onTitleChanged(CharSequence title);
+
+    final CharSequence getTitle() {
+        // If the original window callback is an Activity, we'll use it's title
+        if (mOriginalWindowCallback instanceof Activity) {
+            return ((Activity) mOriginalWindowCallback).getTitle();
+        }
+        // Else, we'll return the title we have recorded ourselves
+        return mTitle;
+    }
+
+    private class AppCompatWindowCallback extends WindowCallbackWrapper {
+        AppCompatWindowCallback(Window.Callback callback) {
+            super(callback);
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            if (AppCompatDelegateImplBase.this.dispatchKeyEvent(event)) {
+                return true;
+            }
+            return super.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public boolean onCreatePanelMenu(int featureId, Menu menu) {
+            if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) {
+                // If this is an options menu but it's not an AppCompat menu, we eat the event
+                // and return false
+                return false;
+            }
+            return super.onCreatePanelMenu(featureId, menu);
+        }
+
+        @Override
+        public boolean onPreparePanel(int featureId, View view, Menu menu) {
+            if (featureId == Window.FEATURE_OPTIONS_PANEL && !(menu instanceof MenuBuilder)) {
+                // If this is an options menu but it's not an AppCompat menu, we eat the event
+                // and return false
+                return false;
+            }
+
+            if (featureId == Window.FEATURE_OPTIONS_PANEL && bypassPrepareOptionsPanelIfNeeded()) {
+                // If this is an options menu and we need to bypass onPreparePanel, do so
+                if (mOriginalWindowCallback instanceof Activity) {
+                    return ((Activity) mOriginalWindowCallback).onPrepareOptionsMenu(menu);
+                } else if (mOriginalWindowCallback instanceof Dialog) {
+                    return ((Dialog) mOriginalWindowCallback).onPrepareOptionsMenu(menu);
+                }
+                return false;
+            }
+
+            // Else, defer to the default handling
+            return super.onPreparePanel(featureId, view, menu);
+        }
+
+        @Override
+        public boolean onMenuOpened(int featureId, Menu menu) {
+            if (AppCompatDelegateImplBase.this.onMenuOpened(featureId, menu)) {
+                return true;
+            }
+            return super.onMenuOpened(featureId, menu);
+        }
+
+        @Override
+        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+            if (AppCompatDelegateImplBase.this.onKeyShortcut(event.getKeyCode(), event)) {
+                return true;
+            }
+            return super.dispatchKeyShortcutEvent(event);
+        }
+
+        @Override
+        public void onContentChanged() {
+            // We purposely do not propagate this call as this is called when we install
+            // our sub-decor rather than the user's content
+        }
+
+        @Override
+        public void onPanelClosed(int featureId, Menu menu) {
+            if (AppCompatDelegateImplBase.this.onPanelClosed(featureId, menu)) {
+                return;
+            }
+            super.onPanelClosed(featureId, menu);
+        }
+
+        /**
+         * For the options menu, we may need to call onPrepareOptionsMenu() directly,
+         * bypassing onPreparePanel(). This is because onPreparePanel() in certain situations
+         * calls menu.hasVisibleItems(), which interferes with any initial invisible items.
+         *
+         * @return true if onPrepareOptionsMenu should be called directly.
+         */
+        private boolean bypassPrepareOptionsPanelIfNeeded() {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN
+                    && mOriginalWindowCallback instanceof Activity) {
+                // For Activities, we only need to bypass onPreparePanel if we're running pre-JB
+                return true;
+            } else if (mOriginalWindowCallback instanceof Dialog) {
+                // For Dialogs, we always need to bypass onPreparePanel
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java
similarity index 64%
rename from v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
rename to v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java
index 0adda0d..a480ee6 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV11.java
@@ -21,25 +21,28 @@
 import android.os.Build;
 import android.support.v7.internal.view.SupportActionModeWrapper;
 import android.support.v7.internal.widget.NativeActionModeAwareLayout;
+import android.util.AttributeSet;
 import android.view.ActionMode;
-import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
 
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-class ActionBarActivityDelegateHC extends ActionBarActivityDelegateBase
+class AppCompatDelegateImplV11 extends AppCompatDelegateImplV7
         implements NativeActionModeAwareLayout.OnActionModeForChildListener {
 
     private NativeActionModeAwareLayout mNativeActionModeAwareLayout;
 
-    ActionBarActivityDelegateHC(ActionBarActivity activity) {
-        super(activity);
+    AppCompatDelegateImplV11(Context context, Window window, AppCompatCallback callback) {
+        super(context, window, callback);
     }
 
     @Override
-    void onSubDecorInstalled() {
+    void onSubDecorInstalled(ViewGroup subDecor) {
         // NativeActionModeAwareLayout is used to notify us when a native Action Mode is started
-        mNativeActionModeAwareLayout = (NativeActionModeAwareLayout) mActivity
-                .findViewById(android.R.id.content);
+        mNativeActionModeAwareLayout = (NativeActionModeAwareLayout)
+                subDecor.findViewById(android.R.id.content);
 
         // Can be null when using FEATURE_ACTION_BAR_OVERLAY
         if (mNativeActionModeAwareLayout != null) {
@@ -47,12 +50,6 @@
         }
     }
 
-    @Override
-    boolean onKeyDown(int keyCode, KeyEvent event) {
-        // On HC+ we do not need to do anything from onKeyDown
-        return false;
-    }
-
     // From NativeActionModeAwareLayout.OnActionModeForChildListener
     @Override
     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
@@ -64,8 +61,25 @@
 
         if (supportActionMode != null) {
             // If we received a support action mode, wrap and return it
-            return new SupportActionModeWrapper(mActivity, supportActionMode);
+            return new SupportActionModeWrapper(mContext, supportActionMode);
         }
         return null;
     }
+
+    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        // First let super have a try, this allows FragmentActivity to inflate any support
+        // fragments
+        final View view = super.callActivityOnCreateView(parent, name, context, attrs);
+        if (view != null) {
+            return view;
+        }
+
+        // Now, let the Activity's LayoutInflater.Factory2 method try...
+        if (mOriginalWindowCallback instanceof LayoutInflater.Factory2) {
+            return ((LayoutInflater.Factory2) mOriginalWindowCallback)
+                    .onCreateView(parent, name, context, attrs);
+        }
+
+        return null;
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
similarity index 65%
rename from v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
rename to v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 6a4f9ba..273beba 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -16,26 +16,32 @@
 
 package android.support.v7.app;
 
+import android.app.Activity;
+import android.app.Dialog;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.media.AudioManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
 import android.support.v4.app.NavUtils;
+import android.support.v4.view.LayoutInflaterCompat;
+import android.support.v4.view.LayoutInflaterFactory;
 import android.support.v4.view.OnApplyWindowInsetsListener;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.ViewConfigurationCompat;
 import android.support.v4.view.WindowInsetsCompat;
 import android.support.v7.appcompat.R;
-import android.support.v7.internal.VersionUtils;
+import android.support.v7.internal.app.TintViewInflater;
 import android.support.v7.internal.app.ToolbarActionBar;
-import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.app.WindowDecorActionBar;
+import android.support.v7.internal.view.ContextThemeWrapper;
 import android.support.v7.internal.view.StandaloneActionMode;
 import android.support.v7.internal.view.menu.ListMenuPresenter;
 import android.support.v7.internal.view.menu.MenuBuilder;
@@ -44,42 +50,44 @@
 import android.support.v7.internal.widget.ActionBarContextView;
 import android.support.v7.internal.widget.DecorContentParent;
 import android.support.v7.internal.widget.FitWindowsViewGroup;
-import android.support.v7.internal.widget.ProgressBarCompat;
-import android.support.v7.internal.widget.TintCheckBox;
-import android.support.v7.internal.widget.TintCheckedTextView;
-import android.support.v7.internal.widget.TintEditText;
-import android.support.v7.internal.widget.TintRadioButton;
-import android.support.v7.internal.widget.TintSpinner;
+import android.support.v7.internal.widget.TintManager;
 import android.support.v7.internal.widget.ViewStubCompat;
 import android.support.v7.internal.widget.ViewUtils;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.Window;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.PopupWindow;
+import android.widget.TextView;
 
 import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR;
 import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR_OVERLAY;
 import static android.support.v4.view.WindowCompat.FEATURE_ACTION_MODE_OVERLAY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.Window.FEATURE_OPTIONS_PANEL;
 
-class ActionBarActivityDelegateBase extends ActionBarActivityDelegate
-        implements MenuBuilder.Callback {
-    private static final String TAG = "ActionBarActivityDelegateBase";
+class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
+        implements MenuBuilder.Callback, LayoutInflaterFactory {
 
     private DecorContentParent mDecorContentParent;
     private ActionMenuPresenterCallback mActionMenuPresenterCallback;
@@ -95,10 +103,9 @@
     private ViewGroup mWindowDecor;
     private ViewGroup mSubDecor;
 
+    private TextView mTitleView;
     private View mStatusGuard;
 
-    private CharSequence mTitleToSet;
-
     // Used to keep track of Progress Bar Window features
     private boolean mFeatureProgress, mFeatureIndeterminateProgress;
 
@@ -125,59 +132,73 @@
 
     private boolean mEnableDefaultActionBarUp;
 
-    private ListMenuPresenter mToolbarListMenuPresenter;
-
     private Rect mTempRect1;
     private Rect mTempRect2;
 
-    ActionBarActivityDelegateBase(ActionBarActivity activity) {
-        super(activity);
+    private TintViewInflater mTintViewInflater;
+
+    AppCompatDelegateImplV7(Context context, Window window, AppCompatCallback callback) {
+        super(context, window, callback);
     }
 
     @Override
-    void onCreate(Bundle savedInstanceState) {
+    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView();
+        mWindowDecor = (ViewGroup) mWindow.getDecorView();
 
-        if (NavUtils.getParentActivityName(mActivity) != null) {
-            ActionBar ab = getSupportActionBar();
-            if (ab == null) {
-                mEnableDefaultActionBarUp = true;
-            } else {
-                ab.setDefaultDisplayHomeAsUpEnabled(true);
+        if (mOriginalWindowCallback instanceof Activity) {
+            if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) {
+                // Peek at the Action Bar and update it if it already exists
+                ActionBar ab = peekSupportActionBar();
+                if (ab == null) {
+                    mEnableDefaultActionBarUp = true;
+                } else {
+                    ab.setDefaultDisplayHomeAsUpEnabled(true);
+                }
             }
         }
     }
 
     @Override
+    public void onPostCreate(Bundle savedInstanceState) {
+        // Make sure that the sub decor is installed
+        ensureSubDecor();
+    }
+
+    @Override
     public ActionBar createSupportActionBar() {
         ensureSubDecor();
-        ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar);
-        ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+        ActionBar ab = null;
+        if (mOriginalWindowCallback instanceof Activity) {
+            ab = new WindowDecorActionBar((Activity) mOriginalWindowCallback, mOverlayActionBar);
+        } else if (mOriginalWindowCallback instanceof Dialog) {
+            ab = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
+        }
+        if (ab != null) {
+            ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+        }
         return ab;
     }
 
     @Override
-    void setSupportActionBar(Toolbar toolbar) {
+    public void setSupportActionBar(Toolbar toolbar) {
+        if (!(mOriginalWindowCallback instanceof Activity)) {
+            // Only Activities support custom Action Bars
+            return;
+        }
+
         final ActionBar ab = getSupportActionBar();
         if (ab instanceof WindowDecorActionBar) {
             throw new IllegalStateException("This Activity already has an action bar supplied " +
                     "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
                     "windowActionBar to false in your theme to use a Toolbar instead.");
-        } else if (ab instanceof ToolbarActionBar) {
-            // Make sure we reset the old toolbar AB's list menu presenter
-            ((ToolbarActionBar) ab).setListMenuPresenter(null);
         }
 
-        // Need to make sure we give the action bar the default window callback. Otherwise multiple
-        // setSupportActionBar() calls lead to memory leaks
-        ToolbarActionBar tbab = new ToolbarActionBar(toolbar, mActivity.getTitle(),
-                mActivity.getWindow(), mDefaultWindowCallback);
-        ensureToolbarListMenuPresenter();
-        tbab.setListMenuPresenter(mToolbarListMenuPresenter);
+        ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
+                mWindow);
         setSupportActionBar(tbab);
-        setWindowCallback(tbab.getWrappedWindowCallback());
+        mWindow.setCallback(tbab.getWrappedWindowCallback());
         tbab.invalidateOptionsMenu();
     }
 
@@ -214,88 +235,90 @@
     @Override
     public void setContentView(View v) {
         ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
         contentParent.removeAllViews();
         contentParent.addView(v);
-        mActivity.onSupportContentChanged();
+        mOriginalWindowCallback.onContentChanged();
     }
 
     @Override
     public void setContentView(int resId) {
         ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
         contentParent.removeAllViews();
-        mActivity.getLayoutInflater().inflate(resId, contentParent);
-        mActivity.onSupportContentChanged();
+        LayoutInflater.from(mContext).inflate(resId, contentParent);
+        mOriginalWindowCallback.onContentChanged();
     }
 
     @Override
     public void setContentView(View v, ViewGroup.LayoutParams lp) {
         ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
         contentParent.removeAllViews();
         contentParent.addView(v, lp);
-        mActivity.onSupportContentChanged();
+        mOriginalWindowCallback.onContentChanged();
     }
 
     @Override
     public void addContentView(View v, ViewGroup.LayoutParams lp) {
         ensureSubDecor();
-        ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
+        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
         contentParent.addView(v, lp);
-        mActivity.onSupportContentChanged();
+        mOriginalWindowCallback.onContentChanged();
     }
 
-    @Override
-    public void onContentChanged() {
-        // Ignore all calls to this method as we call onSupportContentChanged manually above
-    }
-
-    final void ensureSubDecor() {
+    private void ensureSubDecor() {
         if (!mSubDecorInstalled) {
-            if (mHasActionBar) {
-                /**
-                 * This needs some explanation. As we can not use the android:theme attribute
-                 * pre-L, we emulate it by manually creating a LayoutInflater using a
-                 * ContextThemeWrapper pointing to actionBarTheme.
-                 */
-                TypedValue outValue = new TypedValue();
-                mActivity.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
+            final LayoutInflater inflater = LayoutInflater.from(mContext);
 
-                Context themedContext;
-                if (outValue.resourceId != 0) {
-                    themedContext = new ContextThemeWrapper(mActivity, outValue.resourceId);
-                } else {
-                    themedContext = mActivity;
-                }
+            if (!mWindowNoTitle) {
+                if (mIsFloating) {
+                    // If we're floating, inflate the dialog title decor
+                    mSubDecor = (ViewGroup) inflater.inflate(
+                            R.layout.abc_dialog_title_material, null);
+                } else if (mHasActionBar) {
+                    /**
+                     * This needs some explanation. As we can not use the android:theme attribute
+                     * pre-L, we emulate it by manually creating a LayoutInflater using a
+                     * ContextThemeWrapper pointing to actionBarTheme.
+                     */
+                    TypedValue outValue = new TypedValue();
+                    mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
 
-                // Now inflate the view using the themed context and set it as the content view
-                mSubDecor = (ViewGroup) LayoutInflater.from(themedContext)
-                        .inflate(R.layout.abc_screen_toolbar, null);
+                    Context themedContext;
+                    if (outValue.resourceId != 0) {
+                        themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
+                    } else {
+                        themedContext = mContext;
+                    }
 
-                mDecorContentParent = (DecorContentParent) mSubDecor
-                        .findViewById(R.id.decor_content_parent);
-                mDecorContentParent.setWindowCallback(getWindowCallback());
+                    // Now inflate the view using the themed context and set it as the content view
+                    mSubDecor = (ViewGroup) LayoutInflater.from(themedContext)
+                            .inflate(R.layout.abc_screen_toolbar, null);
 
-                /**
-                 * Propagate features to DecorContentParent
-                 */
-                if (mOverlayActionBar) {
-                    mDecorContentParent.initFeature(FEATURE_ACTION_BAR_OVERLAY);
-                }
-                if (mFeatureProgress) {
-                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
-                }
-                if (mFeatureIndeterminateProgress) {
-                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+                    mDecorContentParent = (DecorContentParent) mSubDecor
+                            .findViewById(R.id.decor_content_parent);
+                    mDecorContentParent.setWindowCallback(getWindowCallback());
+
+                    /**
+                     * Propagate features to DecorContentParent
+                     */
+                    if (mOverlayActionBar) {
+                        mDecorContentParent.initFeature(FEATURE_ACTION_BAR_OVERLAY);
+                    }
+                    if (mFeatureProgress) {
+                        mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
+                    }
+                    if (mFeatureIndeterminateProgress) {
+                        mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+                    }
                 }
             } else {
                 if (mOverlayActionMode) {
-                    mSubDecor = (ViewGroup) LayoutInflater.from(mActivity)
-                            .inflate(R.layout.abc_screen_simple_overlay_action_mode, null);
+                    mSubDecor = (ViewGroup) inflater.inflate(
+                            R.layout.abc_screen_simple_overlay_action_mode, null);
                 } else {
-                    mSubDecor = (ViewGroup) LayoutInflater.from(mActivity)
-                            .inflate(R.layout.abc_screen_simple, null);
+                    mSubDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
                 }
 
                 if (Build.VERSION.SDK_INT >= 21) {
@@ -310,14 +333,15 @@
                                     final int newTop = updateStatusGuard(top);
 
                                     if (top != newTop) {
-                                        return insets.replaceSystemWindowInsets(
+                                        insets = insets.replaceSystemWindowInsets(
                                                 insets.getSystemWindowInsetLeft(),
                                                 newTop,
                                                 insets.getSystemWindowInsetRight(),
                                                 insets.getSystemWindowInsetBottom());
-                                    } else {
-                                        return insets;
                                     }
+
+                                    // Now apply the insets on our view
+                                    return ViewCompat.onApplyWindowInsets(v, insets);
                                 }
                             });
                 } else {
@@ -332,17 +356,36 @@
                 }
             }
 
+            if (mSubDecor == null) {
+                throw new IllegalArgumentException(
+                        "AppCompat does not support the current theme features");
+            }
+
+            if (mDecorContentParent == null) {
+                mTitleView = (TextView) mSubDecor.findViewById(R.id.title);
+            }
+
             // Make the decor optionally fit system windows, like the window's decor
             ViewUtils.makeOptionalFitsSystemWindows(mSubDecor);
 
-            // Now set the Activity's content view with the decor
-            mActivity.superSetContentView(mSubDecor);
+            final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content);
+            final ViewGroup abcContent = (ViewGroup) mSubDecor.findViewById(
+                    R.id.action_bar_activity_content);
+
+            // There might be Views already added to the Window's content view so we need to
+            // migrate them to our content view
+            while (decorContent.getChildCount() > 0) {
+                final View child = decorContent.getChildAt(0);
+                decorContent.removeViewAt(0);
+                abcContent.addView(child);
+            }
+
+            // Now set the Window's content view with the decor
+            mWindow.setContentView(mSubDecor);
 
             // Change our content FrameLayout to use the android.R.id.content id.
             // Useful for fragments.
-            final View decorContent = mActivity.findViewById(android.R.id.content);
             decorContent.setId(View.NO_ID);
-            View abcContent = mActivity.findViewById(R.id.action_bar_activity_content);
             abcContent.setId(android.R.id.content);
 
             // The decorContent may have a foreground drawable set (windowContentOverlay).
@@ -351,15 +394,15 @@
                 ((FrameLayout) decorContent).setForeground(null);
             }
 
-            // A title was set before we've install the decor so set it now.
-            if (mTitleToSet != null && mDecorContentParent != null) {
-                mDecorContentParent.setWindowTitle(mTitleToSet);
-                mTitleToSet = null;
+            // If a title was set before we installed the decor, propogate it now
+            CharSequence title = getTitle();
+            if (!TextUtils.isEmpty(title)) {
+                onTitleChanged(title);
             }
 
             applyFixedSizeWindow();
 
-            onSubDecorInstalled();
+            onSubDecorInstalled(mSubDecor);
 
             mSubDecorInstalled = true;
 
@@ -375,10 +418,10 @@
         }
     }
 
-    void onSubDecorInstalled() {}
+    void onSubDecorInstalled(ViewGroup subDecor) {}
 
     private void applyFixedSizeWindow() {
-        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme);
+        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
 
         TypedValue mFixedWidthMajor = null;
         TypedValue mFixedWidthMinor = null;
@@ -402,7 +445,7 @@
             a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor);
         }
 
-        final DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
+        final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
         final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
         int w = ViewGroup.LayoutParams.MATCH_PARENT;
         int h = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -426,107 +469,68 @@
         }
 
         if (w != ViewGroup.LayoutParams.MATCH_PARENT || h != ViewGroup.LayoutParams.MATCH_PARENT) {
-            mActivity.getWindow().setLayout(w, h);
+            mWindow.setLayout(w, h);
         }
 
         a.recycle();
     }
 
     @Override
-    public boolean supportRequestWindowFeature(int featureId) {
+    public boolean requestWindowFeature(int featureId) {
         switch (featureId) {
             case FEATURE_ACTION_BAR:
+                throwFeatureRequestIfSubDecorInstalled();
                 mHasActionBar = true;
                 return true;
             case FEATURE_ACTION_BAR_OVERLAY:
+                throwFeatureRequestIfSubDecorInstalled();
                 mOverlayActionBar = true;
                 return true;
             case FEATURE_ACTION_MODE_OVERLAY:
+                throwFeatureRequestIfSubDecorInstalled();
                 mOverlayActionMode = true;
                 return true;
             case Window.FEATURE_PROGRESS:
+                throwFeatureRequestIfSubDecorInstalled();
                 mFeatureProgress = true;
                 return true;
             case Window.FEATURE_INDETERMINATE_PROGRESS:
+                throwFeatureRequestIfSubDecorInstalled();
                 mFeatureIndeterminateProgress = true;
                 return true;
-            default:
-                return mActivity.requestWindowFeature(featureId);
         }
+
+        return mWindow.requestFeature(featureId);
     }
 
     @Override
-    public void onTitleChanged(CharSequence title) {
+    void onTitleChanged(CharSequence title) {
         if (mDecorContentParent != null) {
             mDecorContentParent.setWindowTitle(title);
         } else if (getSupportActionBar() != null) {
             getSupportActionBar().setWindowTitle(title);
-        } else {
-            mTitleToSet = title;
+        } else if (mTitleView != null) {
+            mTitleView.setText(title);
         }
     }
 
     @Override
-    public View onCreatePanelView(int featureId) {
-        View panelView = null;
-
-        // If there isn't an action mode currently being displayed
-        if (mActionMode == null) {
-            // Let our window callback try first
-            WindowCallback callback = getWindowCallback();
-            if (callback != null) {
-                panelView = callback.onCreatePanelView(featureId);
-            }
-
-            if (panelView == null && mToolbarListMenuPresenter == null) {
-                // Only check our panels if the callback didn't return a view and we do not have
-                // a ListMenuPresenter for Toolbars. We check for the ListMenuPresenter because
-                // once created, Toolbar needs to control the panel view regardless of whether it
-                // has any non-action items to display.
-                PanelFeatureState st = getPanelState(featureId, true);
-                openPanel(st, null);
-                if (st.isOpen) {
-                    panelView = st.shownPanelView;
-                }
-            }
-        }
-        return panelView;
-    }
-
-    @Override
-    public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        if (featureId != Window.FEATURE_OPTIONS_PANEL) {
-            return getWindowCallback().onCreatePanelMenu(featureId, menu);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        if (featureId != Window.FEATURE_OPTIONS_PANEL) {
-            return getWindowCallback().onPreparePanel(featureId, view, menu);
-        }
-        return false;
-    }
-
-    @Override
-    public void onPanelClosed(final int featureId, Menu menu) {
-        PanelFeatureState st = getPanelState(featureId, false);
-        if (st != null) {
-            // If we know about the feature id, update it's state
-            closePanel(st, false);
-        }
-
+    boolean onPanelClosed(final int featureId, Menu menu) {
         if (featureId == FEATURE_ACTION_BAR) {
             ActionBar ab = getSupportActionBar();
             if (ab != null) {
                 ab.dispatchMenuVisibilityChanged(false);
             }
-        } else if (!isDestroyed()) {
-            // Only pass it through to the Activity's super impl if it's not ACTION_BAR. This is
-            // because ICS+ will try and create a framework action bar due to this call
-            mActivity.superOnPanelClosed(featureId, menu);
+            return true;
+        } else if (featureId == FEATURE_OPTIONS_PANEL) {
+            // Make sure that the options panel is closed. This is mainly used when we're using a
+            // ToolbarActionBar
+            PanelFeatureState st = getPanelState(featureId, true);
+            if (st.isOpen) {
+                closePanel(st, false);
+            }
         }
+        return false;
     }
 
     @Override
@@ -537,14 +541,13 @@
                 ab.dispatchMenuVisibilityChanged(true);
             }
             return true;
-        } else {
-            return mActivity.superOnMenuOpened(featureId, menu);
         }
+        return false;
     }
 
     @Override
     public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        final WindowCallback cb = getWindowCallback();
+        final Window.Callback cb = getWindowCallback();
         if (cb != null && !isDestroyed()) {
             final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
             if (panel != null) {
@@ -574,8 +577,8 @@
         ActionBar ab = getSupportActionBar();
         if (ab != null) {
             mActionMode = ab.startActionMode(wrappedCallback);
-            if (mActionMode != null) {
-                mActivity.onSupportActionModeStarted(mActionMode);
+            if (mActionMode != null && mAppCompatCallback != null) {
+                mAppCompatCallback.onSupportActionModeStarted(mActionMode);
             }
         }
 
@@ -588,7 +591,7 @@
     }
 
     @Override
-    public void supportInvalidateOptionsMenu() {
+    public void invalidateOptionsMenu() {
         final ActionBar ab = getSupportActionBar();
         if (ab != null && ab.invalidateOptionsMenu()) return;
 
@@ -613,9 +616,9 @@
                 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
 
                 TypedValue heightValue = new TypedValue();
-                mActivity.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true);
+                mContext.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true);
                 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data,
-                        mActivity.getResources().getDisplayMetrics());
+                        mContext.getResources().getDisplayMetrics());
                 mActionModeView.setContentHeight(height);
                 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
                 mShowActionModePopup = new Runnable() {
@@ -626,7 +629,7 @@
                     }
                 };
             } else {
-                ViewStubCompat stub = (ViewStubCompat) mActivity
+                ViewStubCompat stub = (ViewStubCompat) mSubDecor
                         .findViewById(R.id.action_mode_bar_stub);
                 if (stub != null) {
                     // Set the layout inflater so that it is inflated with the action bar's context
@@ -646,7 +649,7 @@
                 mActionModeView.setVisibility(View.VISIBLE);
                 mActionMode = mode;
                 if (mActionModePopup != null) {
-                    mActivity.getWindow().getDecorView().post(mShowActionModePopup);
+                    mWindow.getDecorView().post(mShowActionModePopup);
                 }
                 mActionModeView.sendAccessibilityEvent(
                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -658,14 +661,13 @@
                 mActionMode = null;
             }
         }
-        if (mActionMode != null && mActivity != null) {
-            mActivity.onSupportActionModeStarted(mActionMode);
+        if (mActionMode != null && mAppCompatCallback != null) {
+            mAppCompatCallback.onSupportActionModeStarted(mActionMode);
         }
         return mActionMode;
     }
 
-    @Override
-    public boolean onBackPressed() {
+    boolean onBackPressed() {
         // Back cancels action modes first.
         if (mActionMode != null) {
             mActionMode.finish();
@@ -678,39 +680,18 @@
             return true;
         }
 
+        // Let the call through...
         return false;
     }
 
     @Override
-    void setSupportProgressBarVisibility(boolean visible) {
-        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
-                Window.PROGRESS_VISIBILITY_OFF);
-    }
-
-    @Override
-    void setSupportProgressBarIndeterminateVisibility(boolean visible) {
-        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
-                Window.PROGRESS_VISIBILITY_OFF);
-    }
-
-    @Override
-    void setSupportProgressBarIndeterminate(boolean indeterminate) {
-        updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON
-                : Window.PROGRESS_INDETERMINATE_OFF);
-    }
-
-    @Override
-    void setSupportProgress(int progress) {
-        updateProgressBars(Window.PROGRESS_START + progress);
-    }
-
-    @Override
-    int getHomeAsUpIndicatorAttrId() {
-        return R.attr.homeAsUpIndicator;
-    }
-
-    @Override
     boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+        // Let the Action Bar have a chance at handling the shortcut
+        ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.onKeyShortcut(keyCode, ev)) {
+            return true;
+        }
+
         // If the panel is already prepared, then perform the shortcut using it.
         boolean handled;
         if (mPreparedPanel != null) {
@@ -741,83 +722,103 @@
     }
 
     @Override
+    boolean dispatchKeyEvent(KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        final int action = event.getAction();
+        final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+        return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event);
+    }
+
+    boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event);
+                return true;
+            case KeyEvent.KEYCODE_BACK:
+                PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+                if (st != null && st.isOpen) {
+                    closePanel(st, true);
+                    return true;
+                }
+                if (onBackPressed()) {
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+
     boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_MENU:
+                onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event);
+                return true;
+        }
+
         // On API v7-10 we need to manually call onKeyShortcut() as this is not called
         // from the Activity
-        return onKeyShortcut(keyCode, event);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            return onKeyShortcut(keyCode, event);
+        }
+        return false;
     }
 
     @Override
-    View createView(final String name, @NonNull AttributeSet attrs) {
-        if (Build.VERSION.SDK_INT < 21) {
-            // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
-            // standard framework versions
-            switch (name) {
-                case "EditText":
-                    return new TintEditText(mActivity, attrs);
-                case "Spinner":
-                    return new TintSpinner(mActivity, attrs);
-                case "CheckBox":
-                    return new TintCheckBox(mActivity, attrs);
-                case "RadioButton":
-                    return new TintRadioButton(mActivity, attrs);
-                case "CheckedTextView":
-                    return new TintCheckedTextView(mActivity, attrs);
-            }
+    public View createView(View parent, final String name, @NonNull Context context,
+            @NonNull AttributeSet attrs) {
+        final boolean isPre21 = Build.VERSION.SDK_INT < 21;
+
+        if (mTintViewInflater == null) {
+            mTintViewInflater = new TintViewInflater(mContext);
         }
-        return null;
+
+        // We only want the View to inherit it's context from the parent if it is from the
+        // apps content, and not part of our sub-decor
+        final boolean inheritContext = isPre21 && mSubDecorInstalled && parent != null
+                && parent.getId() != android.R.id.content;
+
+        return mTintViewInflater.createView(parent, name, context, attrs,
+                inheritContext, isPre21);
+    }
+
+    @Override
+    public void installViewFactory() {
+        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+        if (layoutInflater.getFactory() == null) {
+            LayoutInflaterCompat.setFactory(layoutInflater, this);
+        } else {
+            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+                    + " so we can not install AppCompat's");
+        }
     }
 
     /**
-     * Progress Bar function. Mostly extracted from PhoneWindow.java
+     * From {@link android.support.v4.view.LayoutInflaterFactory}
      */
-    private void updateProgressBars(int value) {
-        ProgressBarCompat circularProgressBar = getCircularProgressBar();
-        ProgressBarCompat horizontalProgressBar = getHorizontalProgressBar();
-
-        if (value == Window.PROGRESS_VISIBILITY_ON) {
-            if (mFeatureProgress) {
-                int level = horizontalProgressBar.getProgress();
-                int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
-                        View.VISIBLE : View.INVISIBLE;
-                horizontalProgressBar.setVisibility(visibility);
-            }
-            if (mFeatureIndeterminateProgress) {
-                circularProgressBar.setVisibility(View.VISIBLE);
-            }
-        } else if (value == Window.PROGRESS_VISIBILITY_OFF) {
-            if (mFeatureProgress) {
-                horizontalProgressBar.setVisibility(View.GONE);
-            }
-            if (mFeatureIndeterminateProgress) {
-                circularProgressBar.setVisibility(View.GONE);
-            }
-        } else if (value == Window.PROGRESS_INDETERMINATE_ON) {
-            horizontalProgressBar.setIndeterminate(true);
-        } else if (value == Window.PROGRESS_INDETERMINATE_OFF) {
-            horizontalProgressBar.setIndeterminate(false);
-        } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) {
-            // We want to set the progress value before testing for visibility
-            // so that when the progress bar becomes visible again, it has the
-            // correct level.
-            horizontalProgressBar.setProgress(value - Window.PROGRESS_START);
-
-            if (value < Window.PROGRESS_END) {
-                showProgressBars(horizontalProgressBar, circularProgressBar);
-            } else {
-                hideProgressBars(horizontalProgressBar, circularProgressBar);
-            }
+    @Override
+    public final View onCreateView(View parent, String name,
+            Context context, AttributeSet attrs) {
+        // First let the Activity's Factory try and inflate the view
+        final View view = callActivityOnCreateView(parent, name, context, attrs);
+        if (view != null) {
+            return view;
         }
+
+        // If the Factory didn't handle it, let our createView() method try
+        return createView(parent, name, context, attrs);
     }
 
-    private void openPanel(int featureId, KeyEvent event) {
-        if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
-                mDecorContentParent.canShowOverflowMenu() &&
-                !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) {
-            mDecorContentParent.showOverflowMenu();
-        } else {
-            openPanel(getPanelState(featureId, true), event);
+    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        // Let the Activity's LayoutInflater.Factory try and handle it
+        if (mOriginalWindowCallback instanceof LayoutInflater.Factory) {
+            final View result = ((LayoutInflater.Factory) mOriginalWindowCallback)
+                    .onCreateView(name, context, attrs);
+            if (result != null) {
+                return result;
+            }
         }
+        return null;
     }
 
     private void openPanel(final PanelFeatureState st, KeyEvent event) {
@@ -829,7 +830,7 @@
         // Don't open an options panel for honeycomb apps on xlarge devices.
         // (The app should be using an action bar for menu items.)
         if (st.featureId == FEATURE_OPTIONS_PANEL) {
-            Context context = mActivity;
+            Context context = mContext;
             Configuration config = context.getResources().getConfiguration();
             boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
                     Configuration.SCREENLAYOUT_SIZE_XLARGE;
@@ -841,42 +842,98 @@
             }
         }
 
-        WindowCallback cb = getWindowCallback();
+        Window.Callback cb = getWindowCallback();
         if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
             // Callback doesn't want the menu to open, reset any state
             closePanel(st, true);
             return;
         }
 
+        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        if (wm == null) {
+            return;
+        }
+
         // Prepare panel (should have been done before, but just in case)
         if (!preparePanel(st, event)) {
             return;
         }
 
+        int width = WRAP_CONTENT;
         if (st.decorView == null || st.refreshDecorView) {
-            initializePanelDecor(st);
-        }
+            if (st.decorView == null) {
+                // Initialize the panel decor, this will populate st.decorView
+                if (!initializePanelDecor(st) || (st.decorView == null))
+                    return;
+            } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+                // Decor needs refreshing, so remove its views
+                st.decorView.removeAllViews();
+            }
 
-        // This will populate st.shownPanelView
-        if (!initializePanelContent(st) || !st.hasPanelItems()) {
-            return;
+            // This will populate st.shownPanelView
+            if (!initializePanelContent(st) || !st.hasPanelItems()) {
+                return;
+            }
+
+            ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+            if (lp == null) {
+                lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+            }
+
+            int backgroundResId = st.background;
+            st.decorView.setBackgroundResource(backgroundResId);
+
+            ViewParent shownPanelParent = st.shownPanelView.getParent();
+            if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
+                ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
+            }
+            st.decorView.addView(st.shownPanelView, lp);
+
+            /*
+             * Give focus to the view, if it or one of its children does not
+             * already have it.
+             */
+            if (!st.shownPanelView.hasFocus()) {
+                st.shownPanelView.requestFocus();
+            }
+        } else if (st.createdPanelView != null) {
+            // If we already had a panel view, carry width=MATCH_PARENT through
+            // as we did above when it was created.
+            ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
+            if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+                width = MATCH_PARENT;
+            }
         }
 
         st.isHandled = false;
+
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                width, WRAP_CONTENT,
+                st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                PixelFormat.TRANSLUCENT);
+
+        lp.gravity = st.gravity;
+        lp.windowAnimations = st.windowAnimations;
+
+        wm.addView(st.decorView, lp);
         st.isOpen = true;
     }
 
-    private void initializePanelDecor(PanelFeatureState st) {
-        st.decorView = mWindowDecor;
+    private boolean initializePanelDecor(PanelFeatureState st) {
         st.setStyle(getActionBarThemedContext());
+        st.decorView = new ListMenuDecorView(st.listPresenterContext);
+        st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+        return true;
     }
 
     private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
         if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() &&
-                (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) ||
+                (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)) ||
                         mDecorContentParent.isOverflowMenuShowPending())) {
 
-            WindowCallback cb = getWindowCallback();
+            final Window.Callback cb = getWindowCallback();
 
             if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
                 if (cb != null && !isDestroyed()) {
@@ -892,7 +949,7 @@
                     // If we don't have a menu or we're waiting for a full content refresh,
                     // forget it. This is a lingering event that no longer matters.
                     if (st.menu != null && !st.refreshMenuContent &&
-                            cb.onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) {
+                            cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
                         cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu);
                         mDecorContentParent.showOverflowMenu();
                     }
@@ -901,7 +958,7 @@
                 mDecorContentParent.hideOverflowMenu();
                 if (!isDestroyed()) {
                     final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
-                    mActivity.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
+                    cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
                 }
             }
             return;
@@ -915,45 +972,8 @@
         openPanel(st, null);
     }
 
-    private void showProgressBars(ProgressBarCompat horizontalProgressBar,
-            ProgressBarCompat spinnyProgressBar) {
-        if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
-            spinnyProgressBar.setVisibility(View.VISIBLE);
-        }
-        // Only show the progress bars if the primary progress is not complete
-        if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) {
-            horizontalProgressBar.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void hideProgressBars(ProgressBarCompat horizontalProgressBar,
-            ProgressBarCompat spinnyProgressBar) {
-        if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) {
-            spinnyProgressBar.setVisibility(View.INVISIBLE);
-        }
-        if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) {
-            horizontalProgressBar.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    private ProgressBarCompat getCircularProgressBar() {
-        ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_circular);
-        if (pb != null) {
-            pb.setVisibility(View.INVISIBLE);
-        }
-        return pb;
-    }
-
-    private ProgressBarCompat getHorizontalProgressBar() {
-        ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_horizontal);
-        if (pb != null) {
-            pb.setVisibility(View.INVISIBLE);
-        }
-        return pb;
-    }
-
     private boolean initializePanelMenu(final PanelFeatureState st) {
-        Context context = mActivity;
+        Context context = mContext;
 
         // If we have an action bar, initialize the menu with the right theme.
         if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) &&
@@ -996,6 +1016,11 @@
     }
 
     private boolean initializePanelContent(PanelFeatureState st) {
+        if (st.createdPanelView != null) {
+            st.shownPanelView = st.createdPanelView;
+            return true;
+        }
+
         if (st.menu == null) {
             return false;
         }
@@ -1026,6 +1051,12 @@
             closePanel(mPreparedPanel, false);
         }
 
+        final Window.Callback cb = getWindowCallback();
+
+        if (cb != null) {
+            st.createdPanelView = cb.onCreatePanelView(st.featureId);
+        }
+
         final boolean isActionBarMenu =
                 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
 
@@ -1035,68 +1066,70 @@
             mDecorContentParent.setMenuPrepared();
         }
 
-        // Init the panel state's menu--return false if init failed
-        if (st.menu == null || st.refreshMenuContent) {
-            if (st.menu == null) {
-                if (!initializePanelMenu(st) || (st.menu == null)) {
-                    return false;
+        if (st.createdPanelView == null) {
+            // Init the panel state's menu--return false if init failed
+            if (st.menu == null || st.refreshMenuContent) {
+                if (st.menu == null) {
+                    if (!initializePanelMenu(st) || (st.menu == null)) {
+                        return false;
+                    }
                 }
-            }
-
-            if (isActionBarMenu && mDecorContentParent != null) {
-                if (mActionMenuPresenterCallback == null) {
-                    mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
-                }
-                mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
-            }
-
-            // Creating the panel menu will involve a lot of manipulation;
-            // don't dispatch change events to presenters until we're done.
-            st.menu.stopDispatchingItemsChanged();
-            if (!getWindowCallback().onCreatePanelMenu(st.featureId, st.menu)) {
-                // Ditch the menu created above
-                st.setMenu(null);
 
                 if (isActionBarMenu && mDecorContentParent != null) {
-                    // Don't show it in the action bar either
-                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+                    if (mActionMenuPresenterCallback == null) {
+                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+                    }
+                    mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
                 }
 
+                // Creating the panel menu will involve a lot of manipulation;
+                // don't dispatch change events to presenters until we're done.
+                st.menu.stopDispatchingItemsChanged();
+                if (!cb.onCreatePanelMenu(st.featureId, st.menu)) {
+                    // Ditch the menu created above
+                    st.setMenu(null);
+
+                    if (isActionBarMenu && mDecorContentParent != null) {
+                        // Don't show it in the action bar either
+                        mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+                    }
+
+                    return false;
+                }
+
+                st.refreshMenuContent = false;
+            }
+
+            // Preparing the panel menu can involve a lot of manipulation;
+            // don't dispatch change events to presenters until we're done.
+            st.menu.stopDispatchingItemsChanged();
+
+            // Restore action view state before we prepare. This gives apps
+            // an opportunity to override frozen/restored state in onPrepare.
+            if (st.frozenActionViewState != null) {
+                st.menu.restoreActionViewStates(st.frozenActionViewState);
+                st.frozenActionViewState = null;
+            }
+
+            // Callback and return if the callback does not want to show the menu
+            if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+                if (isActionBarMenu && mDecorContentParent != null) {
+                    // The app didn't want to show the menu for now but it still exists.
+                    // Clear it out of the action bar.
+                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+                }
+                st.menu.startDispatchingItemsChanged();
                 return false;
             }
 
-            st.refreshMenuContent = false;
-        }
-
-        // Preparing the panel menu can involve a lot of manipulation;
-        // don't dispatch change events to presenters until we're done.
-        st.menu.stopDispatchingItemsChanged();
-
-        // Restore action view state before we prepare. This gives apps
-        // an opportunity to override frozen/restored state in onPrepare.
-        if (st.frozenActionViewState != null) {
-            st.menu.restoreActionViewStates(st.frozenActionViewState);
-            st.frozenActionViewState = null;
-        }
-
-        // Callback and return if the callback does not want to show the menu
-        if (!getWindowCallback().onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) {
-            if (isActionBarMenu && mDecorContentParent != null) {
-                // The app didn't want to show the menu for now but it still exists.
-                // Clear it out of the action bar.
-                mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
-            }
+            // Set the proper keymap
+            KeyCharacterMap kmap = KeyCharacterMap.load(
+                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
+            st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+            st.menu.setQwertyMode(st.qwertyMode);
             st.menu.startDispatchingItemsChanged();
-            return false;
         }
 
-        // Set the proper keymap
-        KeyCharacterMap kmap = KeyCharacterMap.load(
-                event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
-        st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
-        st.menu.setQwertyMode(st.qwertyMode);
-        st.menu.startDispatchingItemsChanged();
-
         // Set other state
         st.isPrepared = true;
         st.isHandled = false;
@@ -1112,13 +1145,17 @@
 
         mClosingActionMenu = true;
         mDecorContentParent.dismissPopups();
-        WindowCallback cb = getWindowCallback();
+        Window.Callback cb = getWindowCallback();
         if (cb != null && !isDestroyed()) {
             cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
         }
         mClosingActionMenu = false;
     }
 
+    private void closePanel(int featureId) {
+        closePanel(getPanelState(featureId, true), true);
+    }
+
     private void closePanel(PanelFeatureState st, boolean doCallback) {
         if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
                 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
@@ -1126,16 +1163,23 @@
             return;
         }
 
-        if (st.isOpen) {
-            if (doCallback) {
-                callOnPanelClosed(st.featureId, st, null);
-            }
+        final boolean wasOpen = st.isOpen;
+
+        final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+        if (wm != null && wasOpen && st.decorView != null) {
+            wm.removeView(st.decorView);
         }
 
         st.isPrepared = false;
         st.isHandled = false;
         st.isOpen = false;
 
+        if (wasOpen && doCallback) {
+            // If the panel was open and we should callback, do so. This should be done after
+            // isOpen is updated to ensure that we do not get into an infinite recursion
+            callOnPanelClosed(st.featureId, st, null);
+        }
+
         // This view is no longer shown, so null it out
         st.shownPanelView = null;
 
@@ -1148,6 +1192,73 @@
         }
     }
 
+    private boolean onKeyDownPanel(int featureId, KeyEvent event) {
+        if (event.getRepeatCount() == 0) {
+            PanelFeatureState st = getPanelState(featureId, true);
+            if (!st.isOpen) {
+                return preparePanel(st, event);
+            }
+        }
+
+        return false;
+    }
+
+    private void onKeyUpPanel(int featureId, KeyEvent event) {
+        if (mActionMode != null) {
+            return;
+        }
+
+        boolean playSoundEffect = false;
+        final PanelFeatureState st = getPanelState(featureId, true);
+        if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+                mDecorContentParent.canShowOverflowMenu() &&
+                !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext))) {
+            if (!mDecorContentParent.isOverflowMenuShowing()) {
+                if (!isDestroyed() && preparePanel(st, event)) {
+                    playSoundEffect = mDecorContentParent.showOverflowMenu();
+                }
+            } else {
+                playSoundEffect = mDecorContentParent.hideOverflowMenu();
+            }
+        } else {
+            if (st.isOpen || st.isHandled) {
+
+                // Play the sound effect if the user closed an open menu (and not if
+                // they just released a menu shortcut)
+                playSoundEffect = st.isOpen;
+
+                // Close menu
+                closePanel(st, true);
+
+            } else if (st.isPrepared) {
+                boolean show = true;
+                if (st.refreshMenuContent) {
+                    // Something may have invalidated the menu since we prepared it.
+                    // Re-prepare it to refresh.
+                    st.isPrepared = false;
+                    show = preparePanel(st, event);
+                }
+
+                if (show) {
+                    // Show menu
+                    openPanel(st, event);
+
+                    playSoundEffect = true;
+                }
+            }
+        }
+
+        if (playSoundEffect) {
+            AudioManager audioManager = (AudioManager) mContext.getSystemService(
+                    Context.AUDIO_SERVICE);
+            if (audioManager != null) {
+                audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+            } else {
+                Log.w(TAG, "Couldn't get audio manager");
+            }
+        }
+    }
+
     private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
         // Try to get a menu
         if (menu == null) {
@@ -1168,7 +1279,10 @@
         if ((panel != null) && (!panel.isOpen))
             return;
 
-        getWindowCallback().onPanelClosed(featureId, menu);
+        Window.Callback cb = getWindowCallback();
+        if (cb != null) {
+            cb.onPanelClosed(featureId, menu);
+        }
     }
 
     private PanelFeatureState findMenuPanel(Menu menu) {
@@ -1200,7 +1314,7 @@
         return st;
     }
 
-    final boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+    private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
             int flags) {
         if (event.isSystem()) {
             return false;
@@ -1292,8 +1406,8 @@
                         mlp.topMargin = insetTop;
 
                         if (mStatusGuard == null) {
-                            mStatusGuard = new View(mActivity);
-                            mStatusGuard.setBackgroundColor(mActivity.getResources()
+                            mStatusGuard = new View(mContext);
+                            mStatusGuard.setBackgroundColor(mContext.getResources()
                                     .getColor(R.color.abc_input_method_navigation_guard));
                             mSubDecor.addView(mStatusGuard, -1,
                                     new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
@@ -1337,22 +1451,17 @@
         return insetTop;
     }
 
-    private void ensureToolbarListMenuPresenter() {
-        if (mToolbarListMenuPresenter == null) {
-            // First resolve panelMenuListTheme
-            TypedValue outValue = new TypedValue();
-            mActivity.getTheme().resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
-
-            Context context = new ContextThemeWrapper(mActivity,
-                    outValue.resourceId != 0
-                            ? outValue.resourceId
-                            : R.style.Theme_AppCompat_CompactMenu);
-
-            mToolbarListMenuPresenter = new ListMenuPresenter(context,
-                    R.layout.abc_list_menu_item_layout);
+    private void throwFeatureRequestIfSubDecorInstalled() {
+        if (mSubDecorInstalled) {
+            throw new AndroidRuntimeException(
+                    "Window feature must be requested before adding content");
         }
     }
 
+    ViewGroup getSubDecor() {
+        return mSubDecor;
+    }
+
     /**
      * Clears out internal reference when the action mode is destroyed.
      */
@@ -1378,7 +1487,7 @@
         public void onDestroyActionMode(ActionMode mode) {
             mWrapped.onDestroyActionMode(mode);
             if (mActionModePopup != null) {
-                mActivity.getWindow().getDecorView().removeCallbacks(mShowActionModePopup);
+                mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
                 mActionModePopup.dismiss();
             } else if (mActionModeView != null) {
                 mActionModeView.setVisibility(View.GONE);
@@ -1389,12 +1498,8 @@
             if (mActionModeView != null) {
                 mActionModeView.removeAllViews();
             }
-            if (mActivity != null) {
-                try {
-                    mActivity.onSupportActionModeFinished(mActionMode);
-                } catch (AbstractMethodError ame) {
-                    // Older apps might not implement this callback method.
-                }
+            if (mAppCompatCallback != null) {
+                mAppCompatCallback.onSupportActionModeFinished(mActionMode);
             }
             mActionMode = null;
         }
@@ -1413,7 +1518,6 @@
                 } else {
                     // Close the panel and only do the callback if the menu is being
                     // closed completely, not if opening a sub menu
-                    mActivity.closeOptionsMenu();
                     closePanel(panel, allMenusAreClosing);
                 }
             }
@@ -1422,7 +1526,7 @@
         @Override
         public boolean onOpenSubMenu(MenuBuilder subMenu) {
             if (subMenu == null && mHasActionBar) {
-                WindowCallback cb = getWindowCallback();
+                Window.Callback cb = getWindowCallback();
                 if (cb != null && !isDestroyed()) {
                     cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
                 }
@@ -1434,7 +1538,7 @@
     private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
         @Override
         public boolean onOpenSubMenu(MenuBuilder subMenu) {
-            WindowCallback cb = getWindowCallback();
+            Window.Callback cb = getWindowCallback();
             if (cb != null) {
                 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
             }
@@ -1452,12 +1556,25 @@
         /** Feature ID for this panel. */
         int featureId;
 
+        int background;
+
+        int gravity;
+
+        int x;
+
+        int y;
+
+        int windowAnimations;
+
         /** Dynamic state of the panel. */
         ViewGroup decorView;
 
         /** The panel that we are actually showing. */
         View shownPanelView;
 
+        /** The panel that was returned by onCreatePanelView(). */
+        View createdPanelView;
+
         /** Use {@link #setMenu} to set this. */
         MenuBuilder menu;
 
@@ -1507,6 +1624,7 @@
 
         public boolean hasPanelItems() {
             if (shownPanelView == null) return false;
+            if (createdPanelView != null) return true;
 
             return listMenuPresenter.getAdapter().getCount() > 0;
         }
@@ -1544,6 +1662,13 @@
             context.getTheme().setTo(widgetTheme);
 
             listPresenterContext = context;
+
+            TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
+            background = a.getResourceId(
+                    R.styleable.Theme_panelBackground, 0);
+            windowAnimations = a.getResourceId(
+                    R.styleable.Theme_android_windowAnimationStyle, 0);
+            a.recycle();
         }
 
         void setMenu(MenuBuilder menu) {
@@ -1646,4 +1771,38 @@
         }
     }
 
+    private class ListMenuDecorView extends FrameLayout {
+        public ListMenuDecorView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            return AppCompatDelegateImplV7.this.dispatchKeyEvent(event);
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent event) {
+            int action = event.getAction();
+            if (action == MotionEvent.ACTION_DOWN) {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                if (isOutOfBounds(x, y)) {
+                    closePanel(Window.FEATURE_OPTIONS_PANEL);
+                    return true;
+                }
+            }
+            return super.onInterceptTouchEvent(event);
+        }
+
+        @Override
+        public void setBackgroundResource(int resid) {
+            setBackgroundDrawable(TintManager.getDrawable(getContext(), resid));
+        }
+
+        private boolean isOutOfBounds(int x, int y) {
+            return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5);
+        }
+    }
+
 }
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java b/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java
new file mode 100644
index 0000000..9039342
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java
@@ -0,0 +1,163 @@
+/*
+ * 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.v7.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.v7.appcompat.R;
+import android.support.v7.view.ActionMode;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class for AppCompat themed {@link android.app.Dialog}s.
+ */
+public class AppCompatDialog extends Dialog implements AppCompatCallback {
+
+    private AppCompatDelegate mDelegate;
+
+    public AppCompatDialog(Context context) {
+        this(context, 0);
+    }
+
+    public AppCompatDialog(Context context, int theme) {
+        super(context, getThemeResId(context, theme));
+
+        // This is a bit weird, but Dialog's are typically created and setup before being shown,
+        // which means that we can't rely on onCreate() being called before a content view is set.
+        // To workaround this, we call onCreate(null) in the ctor, and then again as usual in
+        // onCreate().
+        getDelegate().onCreate(null);
+    }
+
+    protected AppCompatDialog(Context context, boolean cancelable,
+            OnCancelListener cancelListener) {
+        super(context, cancelable, cancelListener);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        super.onCreate(savedInstanceState);
+        getDelegate().onCreate(savedInstanceState);
+    }
+
+    /**
+     * Support library version of {@link android.app.Dialog#getActionBar}.
+     *
+     * <p>Retrieve a reference to this dialog's ActionBar.
+     *
+     * @return The Dialog's ActionBar, or null if it does not have one.
+     */
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        super.setTitle(title);
+        getDelegate().setTitle(title);
+    }
+
+    @Override
+    public void setTitle(int titleId) {
+        super.setTitle(titleId);
+        getDelegate().setTitle(getContext().getString(titleId));
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    /**
+     * Enable extended support library window features.
+     * <p>
+     * This is a convenience for calling
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     * </p>
+     *
+     * @param featureId The desired feature as defined in {@link android.view.Window} or
+     *                  {@link android.support.v4.view.WindowCompat}.
+     * @return Returns true if the requested feature is supported and now enabled.
+     *
+     * @see android.app.Dialog#requestWindowFeature
+     * @see android.view.Window#requestFeature
+     */
+    public boolean supportRequestWindowFeature(int featureId) {
+        return getDelegate().requestWindowFeature(featureId);
+    }
+
+    /**
+     * @hide
+     */
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    /**
+     * @return The {@link AppCompatDelegate} being used by this Dialog.
+     */
+    public AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, this);
+        }
+        return mDelegate;
+    }
+
+    private static int getThemeResId(Context context, int themeId) {
+        if (themeId == 0) {
+            // If the provided theme is 0, then retrieve the dialogTheme from our theme
+            TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
+            themeId = outValue.resourceId;
+        }
+        return themeId;
+    }
+
+    @Override
+    public void onSupportActionModeStarted(ActionMode mode) {
+    }
+
+    @Override
+    public void onSupportActionModeFinished(ActionMode mode) {
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java b/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
index f95e972..06bb360 100644
--- a/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
+++ b/v7/appcompat/src/android/support/v7/app/DrawerArrowDrawable.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -56,6 +57,10 @@
     private boolean mVerticalMirror = false;
     // The interpolated version of the original progress
     private float mProgress;
+    // the amount that overlaps w/ bar size when rotation is max
+    private float mMaxCutForBarSize;
+    // The distance of arrow's center from top when horizontal
+    private float mCenterOffset;
 
     /**
      * @param context used to get the configuration for the drawable from
@@ -68,20 +73,29 @@
         mPaint.setAntiAlias(true);
         mPaint.setColor(typedArray.getColor(R.styleable.DrawerArrowToggle_color, 0));
         mSize = typedArray.getDimensionPixelSize(R.styleable.DrawerArrowToggle_drawableSize, 0);
-        mBarSize = typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0);
-        mTopBottomArrowSize = typedArray
-                .getDimension(R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0);
+        // round this because having this floating may cause bad measurements
+        mBarSize = Math.round(typedArray.getDimension(R.styleable.DrawerArrowToggle_barSize, 0));
+        // round this because having this floating may cause bad measurements
+        mTopBottomArrowSize = Math.round(typedArray.getDimension(
+                R.styleable.DrawerArrowToggle_topBottomBarArrowSize, 0));
         mBarThickness = typedArray.getDimension(R.styleable.DrawerArrowToggle_thickness, 0);
-        mBarGap = typedArray.getDimension(R.styleable.DrawerArrowToggle_gapBetweenBars, 0);
+        // round this because having this floating may cause bad measurements
+        mBarGap = Math.round(typedArray.getDimension(
+                R.styleable.DrawerArrowToggle_gapBetweenBars, 0));
         mSpin = typedArray.getBoolean(R.styleable.DrawerArrowToggle_spinBars, true);
         mMiddleArrowSize = typedArray
                 .getDimension(R.styleable.DrawerArrowToggle_middleBarArrowSize, 0);
+        final int remainingSpace = (int) (mSize - mBarThickness * 3 - mBarGap * 2);
+        mCenterOffset = (remainingSpace / 4) * 2; //making sure it is a multiple of 2.
+        mCenterOffset += mBarThickness * 1.5 + mBarGap;
         typedArray.recycle();
 
         mPaint.setStyle(Paint.Style.STROKE);
-        mPaint.setStrokeJoin(Paint.Join.ROUND);
-        mPaint.setStrokeCap(Paint.Cap.SQUARE);
+        mPaint.setStrokeJoin(Paint.Join.MITER);
+        mPaint.setStrokeCap(Paint.Cap.BUTT);
         mPaint.setStrokeWidth(mBarThickness);
+
+        mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
     }
 
     abstract boolean isLayoutRtl();
@@ -101,43 +115,44 @@
         final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress);
         final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress);
         // Interpolated size of middle bar
-        final float middleBarCut = lerp(0, mBarThickness / 2, mProgress);
+        final float middleBarCut = Math.round(lerp(0, mMaxCutForBarSize, mProgress));
         // The rotation of the top and bottom bars (that make the arrow head)
         final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress);
 
         // The whole canvas rotates as the transition happens
         final float canvasRotate = lerp(isRtl ? 0 : -180, isRtl ? 180 : 0, mProgress);
-        final float topBottomBarOffset = lerp(mBarGap + mBarThickness, 0, mProgress);
+        final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
+        final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
+
+
         mPath.rewind();
+        final float topBottomBarOffset = lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
+                mProgress);
 
         final float arrowEdge = -middleBarSize / 2;
         // draw middle bar
         mPath.moveTo(arrowEdge + middleBarCut, 0);
-        mPath.rLineTo(middleBarSize - middleBarCut, 0);
+        mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
 
-        final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
-        final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
-
-        // top bar
+        // bottom bar
         mPath.moveTo(arrowEdge, topBottomBarOffset);
         mPath.rLineTo(arrowWidth, arrowHeight);
 
-        // bottom bar
+        // top bar
         mPath.moveTo(arrowEdge, -topBottomBarOffset);
         mPath.rLineTo(arrowWidth, -arrowHeight);
-        mPath.moveTo(0, 0);
+
         mPath.close();
 
         canvas.save();
         // Rotate the whole canvas if spinning, if not, rotate it 180 to get
         // the arrow pointing the other way for RTL.
+        canvas.translate(bounds.centerX(), mCenterOffset);
         if (mSpin) {
-            canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1),
-                    bounds.centerX(), bounds.centerY());
+            canvas.rotate(canvasRotate * ((mVerticalMirror ^ isRtl) ? -1 : 1));
         } else if (isRtl) {
-            canvas.rotate(180, bounds.centerX(), bounds.centerY());
+            canvas.rotate(180);
         }
-        canvas.translate(bounds.centerX(), bounds.centerY());
         canvas.drawPath(mPath, mPaint);
 
         canvas.restore();
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java b/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java
similarity index 84%
rename from v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
rename to v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java
index 6a1711e..b97d07c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
+++ b/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.support.v7.internal.widget;
+package android.support.v7.graphics.drawable;
 
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -23,23 +23,23 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableContainer;
-import android.os.Build;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.view.View;
 
 /**
- * Base wrapper that delegates all calls to another {@link Drawable}. The wrapped {@link Drawable}
- * <em>must</em> be fully released from any {@link View} before wrapping, otherwise internal {@link
- * Drawable.Callback} may be dropped.
+ * Drawable which delegates all calls to it's wrapped {@link Drawable}.
+ * <p>
+ * The wrapped {@link Drawable} <em>must</em> be fully released from any {@link View}
+ * before wrapping, otherwise internal {@link Drawable.Callback} may be dropped.
+ *
+ * @hide
  */
-class DrawableWrapper extends Drawable implements Drawable.Callback {
+public class DrawableWrapper extends Drawable implements Drawable.Callback {
 
-    private final Drawable mDrawable;
+    private Drawable mDrawable;
 
     public DrawableWrapper(Drawable drawable) {
-        mDrawable = drawable;
-        mDrawable.setCallback(this);
+        setWrappedDrawable(drawable);
     }
 
     @Override
@@ -48,9 +48,8 @@
     }
 
     @Override
-    public void setBounds(int left, int top, int right, int bottom) {
-        super.setBounds(left, top, right, bottom);
-        mDrawable.setBounds(left, top, right, bottom);
+    protected void onBoundsChange(Rect bounds) {
+        mDrawable.setBounds(bounds);
     }
 
     @Override
@@ -207,4 +206,20 @@
     public void setHotspotBounds(int left, int top, int right, int bottom) {
         DrawableCompat.setHotspotBounds(mDrawable, left, top, right, bottom);
     }
+
+    public Drawable getWrappedDrawable() {
+        return mDrawable;
+    }
+
+    public void setWrappedDrawable(Drawable drawable) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+        }
+
+        mDrawable = drawable;
+
+        if (drawable != null) {
+            drawable.setCallback(this);
+        }
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/app/TintViewInflater.java b/v7/appcompat/src/android/support/v7/internal/app/TintViewInflater.java
new file mode 100644
index 0000000..9f59256
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/app/TintViewInflater.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.app;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.internal.widget.TintAutoCompleteTextView;
+import android.support.v7.internal.widget.TintButton;
+import android.support.v7.internal.widget.TintCheckBox;
+import android.support.v7.internal.widget.TintCheckedTextView;
+import android.support.v7.internal.widget.TintEditText;
+import android.support.v7.internal.widget.TintMultiAutoCompleteTextView;
+import android.support.v7.internal.widget.TintRadioButton;
+import android.support.v7.internal.widget.TintRatingBar;
+import android.support.v7.internal.widget.TintSpinner;
+import android.support.v7.internal.widget.ViewUtils;
+import android.util.AttributeSet;
+import android.view.InflateException;
+import android.view.View;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class is responsible for manually inflating our tinted widgets which are used on devices
+ * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
+ * should only be used when running on those devices.
+ * <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
+ * the framework versions in layout inflation; the second is backport the {@code android:theme}
+ * functionality for any inflated widgets. This include theme inheritance from it's parent.
+ *
+ * @hide
+ */
+public class TintViewInflater {
+
+    static final Class<?>[] sConstructorSignature = new Class[] {
+            Context.class, AttributeSet.class};
+
+    private static final Map<String, Constructor<? extends View>> sConstructorMap = new HashMap<>();
+
+    private final Context mContext;
+    private final Object[] mConstructorArgs = new Object[2];
+
+    public TintViewInflater(Context context) {
+        mContext = context;
+    }
+
+    public final View createView(View parent, final String name, @NonNull Context context,
+            @NonNull AttributeSet attrs, boolean inheritContext, boolean themeContext) {
+        final Context originalContext = context;
+
+        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
+        // by using the parent's context
+        if (inheritContext && parent != null) {
+            context = parent.getContext();
+        }
+        if (themeContext) {
+            // We then apply the theme on the context, if specified
+            context = ViewUtils.themifyContext(context, attrs, true, true);
+        }
+
+        // We need to 'inject' our tint aware Views in place of the standard framework versions
+        switch (name) {
+            case "EditText":
+                return new TintEditText(context, attrs);
+            case "Spinner":
+                return new TintSpinner(context, attrs);
+            case "CheckBox":
+                return new TintCheckBox(context, attrs);
+            case "RadioButton":
+                return new TintRadioButton(context, attrs);
+            case "CheckedTextView":
+                return new TintCheckedTextView(context, attrs);
+            case "AutoCompleteTextView":
+                return new TintAutoCompleteTextView(context, attrs);
+            case "MultiAutoCompleteTextView":
+                return new TintMultiAutoCompleteTextView(context, attrs);
+            case "RatingBar":
+                return new TintRatingBar(context, attrs);
+            case "Button":
+                return new TintButton(context, attrs);
+        }
+
+        if (originalContext != context) {
+            // If the original context does not equal our themed context, then we need to manually
+            // inflate it using the name so that app:theme takes effect.
+            return createViewFromTag(context, name, attrs);
+        }
+
+        return null;
+    }
+
+    private View createViewFromTag(Context context, String name, AttributeSet attrs) {
+        if (name.equals("view")) {
+            name = attrs.getAttributeValue(null, "class");
+        }
+
+        try {
+            mConstructorArgs[0] = context;
+            mConstructorArgs[1] = attrs;
+
+            if (-1 == name.indexOf('.')) {
+                // try the android.widget prefix first...
+                return createView(name, "android.widget.");
+            } else {
+                return createView(name, null);
+            }
+        } catch (Exception e) {
+            // We do not want to catch these, lets return null and let the actual LayoutInflater
+            // try
+            return null;
+        } finally {
+            // Don't retain static reference on context.
+            mConstructorArgs[0] = null;
+            mConstructorArgs[1] = null;
+        }
+    }
+
+    private View createView(String name, String prefix)
+            throws ClassNotFoundException, InflateException {
+        Constructor<? extends View> constructor = sConstructorMap.get(name);
+
+        try {
+            if (constructor == null) {
+                // Class not found in the cache, see if it's real, and try to add it
+                Class<? extends View> clazz = mContext.getClassLoader().loadClass(
+                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
+
+                constructor = clazz.getConstructor(sConstructorSignature);
+                sConstructorMap.put(name, constructor);
+            }
+            constructor.setAccessible(true);
+            return constructor.newInstance(mConstructorArgs);
+        } catch (Exception e) {
+            // We do not want to catch these, lets return null and let the actual LayoutInflater
+            // try
+            return null;
+        }
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
index 08f8552..9653f42 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-
 package android.support.v7.internal.app;
 
 import android.content.Context;
@@ -25,15 +24,14 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.app.ActionBar;
+import android.support.v7.internal.view.WindowCallbackWrapper;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.view.menu.ListMenuPresenter;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.support.v7.internal.widget.DecorToolbar;
 import android.support.v7.internal.widget.ToolbarWidgetWrapper;
-import android.support.v7.view.ActionMode;
 import android.support.v7.widget.Toolbar;
-import android.support.v7.widget.WindowCallbackWrapper;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
@@ -50,10 +48,9 @@
  * @hide
  */
 public class ToolbarActionBar extends ActionBar {
-    private Toolbar mToolbar;
     private DecorToolbar mDecorToolbar;
     private boolean mToolbarMenuPrepared;
-    private WindowCallback mWindowCallback;
+    private Window.Callback mWindowCallback;
     private boolean mMenuCallbackSet;
 
     private boolean mLastMenuVisibility;
@@ -78,11 +75,9 @@
                 }
             };
 
-    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window,
-            WindowCallback windowCallback) {
-        mToolbar = toolbar;
+    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window) {
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
-        mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
+        mWindowCallback = new ToolbarCallbackWrapper(window.getCallback());
         mDecorToolbar.setWindowCallback(mWindowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
@@ -90,7 +85,7 @@
         mWindow = window;
     }
 
-    public WindowCallback getWrappedWindowCallback() {
+    public Window.Callback getWrappedWindowCallback() {
         return mWindowCallback;
     }
 
@@ -107,8 +102,8 @@
 
     @Override
     public void setCustomView(int resId) {
-        final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext());
-        setCustomView(inflater.inflate(resId, mToolbar, false));
+        final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
+        setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
     }
 
     @Override
@@ -148,17 +143,17 @@
 
     @Override
     public void setElevation(float elevation) {
-        ViewCompat.setElevation(mToolbar, elevation);
+        ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation);
     }
 
     @Override
     public float getElevation() {
-        return ViewCompat.getElevation(mToolbar);
+        return ViewCompat.getElevation(mDecorToolbar.getViewGroup());
     }
 
     @Override
     public Context getThemedContext() {
-        return mToolbar.getContext();
+        return mDecorToolbar.getContext();
     }
 
     @Override
@@ -168,12 +163,12 @@
 
     @Override
     public void setHomeAsUpIndicator(Drawable indicator) {
-        mToolbar.setNavigationIcon(indicator);
+        mDecorToolbar.setNavigationIcon(indicator);
     }
 
     @Override
     public void setHomeAsUpIndicator(int resId) {
-        mToolbar.setNavigationIcon(resId);
+        mDecorToolbar.setNavigationIcon(resId);
     }
 
     @Override
@@ -202,11 +197,6 @@
     }
 
     @Override
-    public ActionMode startActionMode(ActionMode.Callback callback) {
-        return mWindowCallback.startActionMode(callback);
-    }
-
-    @Override
     public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
         mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
     }
@@ -296,7 +286,7 @@
 
     @Override
     public void setBackgroundDrawable(@Nullable Drawable d) {
-        mToolbar.setBackgroundDrawable(d);
+        mDecorToolbar.setBackgroundDrawable(d);
     }
 
     @Override
@@ -306,12 +296,12 @@
 
     @Override
     public CharSequence getTitle() {
-        return mToolbar.getTitle();
+        return mDecorToolbar.getTitle();
     }
 
     @Override
     public CharSequence getSubtitle() {
-        return mToolbar.getSubtitle();
+        return mDecorToolbar.getSubtitle();
     }
 
     @Override
@@ -405,44 +395,44 @@
 
     @Override
     public int getHeight() {
-        return mToolbar.getHeight();
+        return mDecorToolbar.getHeight();
     }
 
     @Override
     public void show() {
         // TODO: Consider a better transition for this.
         // Right now use no automatic transition so that the app can supply one if desired.
-        mToolbar.setVisibility(View.VISIBLE);
+        mDecorToolbar.setVisibility(View.VISIBLE);
     }
 
     @Override
     public void hide() {
         // TODO: Consider a better transition for this.
         // Right now use no automatic transition so that the app can supply one if desired.
-        mToolbar.setVisibility(View.GONE);
+        mDecorToolbar.setVisibility(View.GONE);
     }
 
     @Override
     public boolean isShowing() {
-        return mToolbar.getVisibility() == View.VISIBLE;
+        return mDecorToolbar.getVisibility() == View.VISIBLE;
     }
 
     @Override
     public boolean openOptionsMenu() {
-        return mToolbar.showOverflowMenu();
+        return mDecorToolbar.showOverflowMenu();
     }
 
     @Override
     public boolean invalidateOptionsMenu() {
-        mToolbar.removeCallbacks(mMenuInvalidator);
-        ViewCompat.postOnAnimation(mToolbar, mMenuInvalidator);
+        mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
+        ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator);
         return true;
     }
 
     @Override
     public boolean collapseActionView() {
-        if (mToolbar.hasExpandedActionView()) {
-            mToolbar.collapseActionView();
+        if (mDecorToolbar.hasExpandedActionView()) {
+            mDecorToolbar.collapseActionView();
             return true;
         }
         return false;
@@ -475,6 +465,12 @@
         return true;
     }
 
+    @Override
+    public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
+        Menu menu = getMenu();
+        return menu != null ? menu.performShortcut(keyCode, ev, 0) : false;
+    }
+
     public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
         mMenuVisibilityListeners.add(listener);
     }
@@ -496,18 +492,47 @@
     }
 
     private View getListMenuView(Menu menu) {
+        ensureListMenuPresenter(menu);
+
         if (menu == null || mListMenuPresenter == null) {
             return null;
         }
 
         if (mListMenuPresenter.getAdapter().getCount() > 0) {
-            return (View) mListMenuPresenter.getMenuView(mToolbar);
+            return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup());
         }
         return null;
     }
 
+    private void ensureListMenuPresenter(Menu menu) {
+        if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) {
+            MenuBuilder mb = (MenuBuilder) menu;
+
+            Context context = mDecorToolbar.getContext();
+            final TypedValue outValue = new TypedValue();
+            final Resources.Theme widgetTheme = context.getResources().newTheme();
+            widgetTheme.setTo(context.getTheme());
+
+            // Apply the panelMenuListTheme
+            widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
+            if (outValue.resourceId != 0) {
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            } else {
+                widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
+            }
+
+            context = new ContextThemeWrapper(context, 0);
+            context.getTheme().setTo(widgetTheme);
+
+            // Finally create the list menu presenter
+            mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout);
+            mListMenuPresenter.setCallback(new PanelMenuPresenterCallback());
+            mb.addMenuPresenter(mListMenuPresenter);
+        }
+    }
+
     private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
-        public ToolbarCallbackWrapper(WindowCallback wrapped) {
+        public ToolbarCallbackWrapper(Window.Callback wrapped) {
             super(wrapped);
         }
 
@@ -525,20 +550,9 @@
         public View onCreatePanelView(int featureId) {
             switch (featureId) {
                 case Window.FEATURE_OPTIONS_PANEL:
-                    if (!mToolbarMenuPrepared) {
-                        // If the options menu isn't populated yet, do it now
-                        populateOptionsMenu();
-                        mToolbar.removeCallbacks(mMenuInvalidator);
-                    }
-
-                    if (mToolbarMenuPrepared && mWindowCallback != null) {
-                        // If we are prepared, check to see if the callback wants a menu opened
-                        final Menu menu = getMenu();
-
-                        if (mWindowCallback.onPreparePanel(featureId, null, menu) &&
-                                mWindowCallback.onMenuOpened(featureId, menu)) {
-                            return getListMenuView(menu);
-                        }
+                    final Menu menu = mDecorToolbar.getMenu();
+                    if (onPreparePanel(featureId, null, menu) && onMenuOpened(featureId, menu)) {
+                        return getListMenuView(menu);
                     }
                     break;
             }
@@ -548,31 +562,11 @@
 
     private Menu getMenu() {
         if (!mMenuCallbackSet) {
-            mToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback());
+            mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(),
+                    new MenuBuilderCallback());
             mMenuCallbackSet = true;
         }
-        return mToolbar.getMenu();
-    }
-
-    public void setListMenuPresenter(ListMenuPresenter listMenuPresenter) {
-        final Menu menu = getMenu();
-
-        if (menu instanceof MenuBuilder) {
-            MenuBuilder mb = (MenuBuilder) menu;
-
-            if (mListMenuPresenter != null) {
-                // We currently have a list menu presenter, remove it as our menu's presenter
-                mListMenuPresenter.setCallback(null);
-                mb.removeMenuPresenter(mListMenuPresenter);
-            }
-
-            mListMenuPresenter = listMenuPresenter;
-
-            if (listMenuPresenter != null) {
-                listMenuPresenter.setCallback(new PanelMenuPresenterCallback());
-                mb.addMenuPresenter(listMenuPresenter);
-            }
-        }
+        return mDecorToolbar.getMenu();
     }
 
     private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
@@ -594,7 +588,7 @@
             }
 
             mClosingActionMenu = true;
-            mToolbar.dismissPopupMenus();
+            mDecorToolbar.dismissPopupMenus();
             if (mWindowCallback != null) {
                 mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
             }
@@ -608,9 +602,6 @@
             if (mWindowCallback != null) {
                 mWindowCallback.onPanelClosed(Window.FEATURE_OPTIONS_PANEL, menu);
             }
-
-            // Close the options panel
-            mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
         }
 
         @Override
@@ -632,7 +623,7 @@
         @Override
         public void onMenuModeChange(MenuBuilder menu) {
             if (mWindowCallback != null) {
-                if (mToolbar.isOverflowMenuShowing()) {
+                if (mDecorToolbar.isOverflowMenuShowing()) {
                     mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
                 } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
                         null, menu)) {
diff --git a/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java b/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java
deleted file mode 100644
index 5137f16..0000000
--- a/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.internal.app;
-
-import android.support.v7.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-/**
- * Interface which allows us to intercept and control calls to {@link android.view.Window.Callback}.
- * Used by ActionBarActivityDelegates.
- *
- * @hide
- */
-public interface WindowCallback {
-
-    boolean onMenuItemSelected(int featureId, MenuItem menuItem);
-
-    boolean onCreatePanelMenu(int featureId, Menu menu);
-
-    boolean onPreparePanel(int featureId, View menuView, Menu menu);
-
-    void onPanelClosed(int featureId, Menu menu);
-
-    boolean onMenuOpened(int featureId, Menu menu);
-
-    ActionMode startActionMode(ActionMode.Callback callback);
-
-    View onCreatePanelView(int featureId);
-
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
index 96ed48f..ef196a4 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.internal.app;
 
+import android.app.Activity;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -31,7 +32,6 @@
 import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
 import android.support.v4.view.ViewPropertyAnimatorUpdateListener;
 import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.view.ActionBarPolicy;
 import android.support.v7.internal.view.ViewPropertyAnimatorCompatSet;
@@ -83,7 +83,7 @@
 
     private Context mContext;
     private Context mThemedContext;
-    private FragmentActivity mActivity;
+    private Activity mActivity;
     private Dialog mDialog;
 
     private ActionBarOverlayLayout mOverlayLayout;
@@ -169,7 +169,7 @@
                 }
             };
 
-    public WindowDecorActionBar(ActionBarActivity activity, boolean overlayMode) {
+    public WindowDecorActionBar(Activity activity, boolean overlayMode) {
         mActivity = activity;
         Window window = activity.getWindow();
         View decor = window.getDecorView();
@@ -506,7 +506,7 @@
 
         mOverlayLayout.setHideOnContentScrollEnabled(false);
         mContextView.killMode();
-        ActionModeImpl mode = new ActionModeImpl(callback);
+        ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback);
         if (mode.dispatchOnCreate()) {
             mode.invalidate();
             mContextView.initForMode(mode);
@@ -616,8 +616,14 @@
             return;
         }
 
-        final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null :
-                mActivity.getSupportFragmentManager().beginTransaction().disallowAddToBackStack();
+        final FragmentTransaction trans;
+        if (mActivity instanceof FragmentActivity && !mDecorToolbar.getViewGroup().isInEditMode()) {
+            // If we're not in edit mode and our Activity is a FragmentActivity, start a tx
+            trans = ((FragmentActivity) mActivity).getSupportFragmentManager()
+                    .beginTransaction().disallowAddToBackStack();
+        } else {
+            trans = null;
+        }
 
         if (mSelectedTab == tab) {
             if (mSelectedTab != null) {
@@ -944,20 +950,23 @@
      * @hide
      */
     public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
+        private final Context mActionModeContext;
+        private final MenuBuilder mMenu;
+
         private ActionMode.Callback mCallback;
-        private MenuBuilder mMenu;
         private WeakReference<View> mCustomView;
 
-        public ActionModeImpl(ActionMode.Callback callback) {
+        public ActionModeImpl(Context context, ActionMode.Callback callback) {
+            mActionModeContext = context;
             mCallback = callback;
-            mMenu = new MenuBuilder(getThemedContext())
+            mMenu = new MenuBuilder(context)
                     .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
             mMenu.setCallback(this);
         }
 
         @Override
         public MenuInflater getMenuInflater() {
-            return new SupportMenuInflater(getThemedContext());
+            return new SupportMenuInflater(mActionModeContext);
         }
 
         @Override
@@ -998,6 +1007,13 @@
 
         @Override
         public void invalidate() {
+            if (mActionMode != this) {
+                // Not the active action mode - no-op. It's possible we are
+                // currently deferring onDestroy, so the app doesn't yet know we
+                // are going away and is trying to use us. That's also a no-op.
+                return;
+            }
+
             mMenu.stopDispatchingItemsChanged();
             try {
                 mCallback.onPrepareActionMode(this, mMenu);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java
new file mode 100644
index 0000000..e414203
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/view/ContextThemeWrapper.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.view;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.support.v7.appcompat.R;
+import android.view.LayoutInflater;
+
+/**
+ * A ContextWrapper that allows you to modify the theme from what is in the
+ * wrapped context.
+ *
+ * @hide
+ */
+public class ContextThemeWrapper extends ContextWrapper {
+    private int mThemeResource;
+    private Resources.Theme mTheme;
+    private LayoutInflater mInflater;
+
+    public ContextThemeWrapper(Context base, int themeres) {
+        super(base);
+        mThemeResource = themeres;
+    }
+
+    @Override
+    public void setTheme(int resid) {
+        mThemeResource = resid;
+        initializeTheme();
+    }
+
+    public int getThemeResId() {
+        return mThemeResource;
+    }
+
+    @Override
+    public Resources.Theme getTheme() {
+        if (mTheme != null) {
+            return mTheme;
+        }
+
+        if (mThemeResource == 0) {
+            mThemeResource = R.style.Theme_AppCompat_Light;
+        }
+        initializeTheme();
+
+        return mTheme;
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+            if (mInflater == null) {
+                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+            }
+            return mInflater;
+        }
+        return getBaseContext().getSystemService(name);
+    }
+
+    /**
+     * Called by {@link #setTheme} and {@link #getTheme} to apply a theme
+     * resource to the current Theme object.  Can override to change the
+     * default (simple) behavior.  This method will not be called in multiple
+     * threads simultaneously.
+     *
+     * @param theme The Theme object being modified.
+     * @param resid The theme style resource being applied to <var>theme</var>.
+     * @param first Set to true if this is the first time a style is being
+     *              applied to <var>theme</var>.
+     */
+    protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
+        theme.applyStyle(resid, true);
+    }
+
+    private void initializeTheme() {
+        final boolean first = mTheme == null;
+        if (first) {
+            mTheme = getResources().newTheme();
+            Resources.Theme theme = getBaseContext().getTheme();
+            if (theme != null) {
+                mTheme.setTo(theme);
+            }
+        }
+        onApplyThemeResource(mTheme, mThemeResource, first);
+    }
+}
+
diff --git a/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
index ecd499c..029ad97 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
@@ -19,6 +19,8 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Build;
+import android.support.v4.internal.view.SupportMenu;
+import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.util.SimpleArrayMap;
 import android.support.v7.internal.view.menu.MenuWrapperFactory;
 import android.view.ActionMode;
@@ -35,14 +37,13 @@
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public class SupportActionModeWrapper extends ActionMode {
 
-    final MenuInflater mInflater;
-
+    final Context mContext;
     final android.support.v7.view.ActionMode mWrappedObject;
 
     public SupportActionModeWrapper(Context context,
             android.support.v7.view.ActionMode supportActionMode) {
+        mContext = context;
         mWrappedObject = supportActionMode;
-        mInflater = new SupportMenuInflater(context);
     }
 
     @Override
@@ -77,7 +78,7 @@
 
     @Override
     public Menu getMenu() {
-        return MenuWrapperFactory.createMenuWrapper(mWrappedObject.getMenu());
+        return MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) mWrappedObject.getMenu());
     }
 
     @Override
@@ -112,7 +113,7 @@
 
     @Override
     public MenuInflater getMenuInflater() {
-        return mInflater;
+        return mWrappedObject.getMenuInflater();
     }
 
     @Override
@@ -139,30 +140,32 @@
 
         final SimpleArrayMap<android.support.v7.view.ActionMode, SupportActionModeWrapper>
                 mActionModes;
+        final SimpleArrayMap<Menu, Menu> mMenus;
 
         public CallbackWrapper(Context context, Callback supportCallback) {
             mContext = context;
             mWrappedCallback = supportCallback;
             mActionModes = new SimpleArrayMap<>();
+            mMenus = new SimpleArrayMap<>();
         }
 
         @Override
         public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
             return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuWrapper(menu));
+                    getMenuWrapper(menu));
         }
 
         @Override
         public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
             return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuWrapper(menu));
+                    getMenuWrapper(menu));
         }
 
         @Override
         public boolean onActionItemClicked(android.support.v7.view.ActionMode mode,
                 android.view.MenuItem item) {
             return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuItemWrapper(item));
+                    MenuWrapperFactory.wrapSupportMenuItem(mContext, (SupportMenuItem) item));
         }
 
         @Override
@@ -170,6 +173,15 @@
             mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode));
         }
 
+        private Menu getMenuWrapper(Menu menu) {
+            Menu wrapper = mMenus.get(menu);
+            if (wrapper == null) {
+                wrapper = MenuWrapperFactory.wrapSupportMenu(mContext, (SupportMenu) menu);
+                mMenus.put(menu, wrapper);
+            }
+            return wrapper;
+        }
+
         private ActionMode getActionModeWrapper(android.support.v7.view.ActionMode mode) {
             // First see if we already have a wrapper for this mode
             SupportActionModeWrapper wrapper = mActionModes.get(mode);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java
new file mode 100644
index 0000000..d799d00
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/view/WindowCallbackWrapper.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.view;
+
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * A simple decorator stub for Window.Callback that passes through any calls
+ * to the wrapped instance as a base implementation. Call super.foo() to call into
+ * the wrapped callback for any subclasses.
+ *
+ * @hide
+ */
+public class WindowCallbackWrapper implements Window.Callback {
+
+    final Window.Callback mWrapped;
+
+    public WindowCallbackWrapper(Window.Callback wrapped) {
+        if (wrapped == null) {
+            throw new IllegalArgumentException("Window callback may not be null");
+        }
+        mWrapped = wrapped;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mWrapped.dispatchKeyEvent(event);
+    }
+
+    @Override
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        return mWrapped.dispatchKeyShortcutEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return mWrapped.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTrackballEvent(MotionEvent event) {
+        return mWrapped.dispatchTrackballEvent(event);
+    }
+
+    @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        return mWrapped.dispatchGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return mWrapped.dispatchPopulateAccessibilityEvent(event);
+    }
+
+    @Override
+    public View onCreatePanelView(int featureId) {
+        return mWrapped.onCreatePanelView(featureId);
+    }
+
+    @Override
+    public boolean onCreatePanelMenu(int featureId, Menu menu) {
+        return mWrapped.onCreatePanelMenu(featureId, menu);
+    }
+
+    @Override
+    public boolean onPreparePanel(int featureId, View view, Menu menu) {
+        return mWrapped.onPreparePanel(featureId, view, menu);
+    }
+
+    @Override
+    public boolean onMenuOpened(int featureId, Menu menu) {
+        return mWrapped.onMenuOpened(featureId, menu);
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        return mWrapped.onMenuItemSelected(featureId, item);
+    }
+
+    @Override
+    public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+        mWrapped.onWindowAttributesChanged(attrs);
+    }
+
+    @Override
+    public void onContentChanged() {
+        mWrapped.onContentChanged();
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        mWrapped.onWindowFocusChanged(hasFocus);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        mWrapped.onAttachedToWindow();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mWrapped.onDetachedFromWindow();
+    }
+
+    @Override
+    public void onPanelClosed(int featureId, Menu menu) {
+        mWrapped.onPanelClosed(featureId, menu);
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        return mWrapped.onSearchRequested();
+    }
+
+    @Override
+    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+        return mWrapped.onWindowStartingActionMode(callback);
+    }
+
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+        mWrapped.onActionModeStarted(mode);
+    }
+
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+        mWrapped.onActionModeFinished(mode);
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
index 0893673..8755ef2 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
@@ -89,8 +89,6 @@
         setOnClickListener(this);
         setOnLongClickListener(this);
 
-        setTransformationMethod(new AllCapsTransformationMethod(context));
-
         mSavedPaddingLeft = -1;
     }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
index cfd5b85..b8f7793 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuWrapper.java
@@ -16,58 +16,69 @@
 
 package android.support.v7.internal.view.menu;
 
+import android.content.Context;
 import android.support.v4.internal.view.SupportMenuItem;
+import android.support.v4.internal.view.SupportSubMenu;
+import android.support.v4.util.ArrayMap;
 import android.view.MenuItem;
 import android.view.SubMenu;
 
-import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 
 abstract class BaseMenuWrapper<T> extends BaseWrapper<T> {
 
-    private HashMap<MenuItem, SupportMenuItem> mMenuItems;
+    final Context mContext;
 
-    private HashMap<SubMenu, SubMenu> mSubMenus;
+    private Map<SupportMenuItem, MenuItem> mMenuItems;
+    private Map<SupportSubMenu, SubMenu> mSubMenus;
 
-    BaseMenuWrapper(T object) {
+    BaseMenuWrapper(Context context, T object) {
         super(object);
+        mContext = context;
     }
 
-    final SupportMenuItem getMenuItemWrapper(android.view.MenuItem frameworkItem) {
-        if (frameworkItem != null) {
-            // Instantiate HashMap if null
+    final MenuItem getMenuItemWrapper(final MenuItem menuItem) {
+        if (menuItem instanceof SupportMenuItem) {
+            final SupportMenuItem supportMenuItem = (SupportMenuItem) menuItem;
+
+            // Instantiate Map if null
             if (mMenuItems == null) {
-                mMenuItems = new HashMap<MenuItem, SupportMenuItem>();
+                mMenuItems = new ArrayMap<>();
             }
 
-            SupportMenuItem compatItem = mMenuItems.get(frameworkItem);
+            // First check if we already have a wrapper for this item
+            MenuItem wrappedItem = mMenuItems.get(menuItem);
 
-            if (null == compatItem) {
-                compatItem = MenuWrapperFactory.createSupportMenuItemWrapper(frameworkItem);
-                mMenuItems.put(frameworkItem, compatItem);
+            if (null == wrappedItem) {
+                // ... if not, create one and add it to our map
+                wrappedItem = MenuWrapperFactory.wrapSupportMenuItem(mContext, supportMenuItem);
+                mMenuItems.put(supportMenuItem, wrappedItem);
             }
 
-            return compatItem;
+            return wrappedItem;
         }
-        return null;
+        return menuItem;
     }
 
-    final SubMenu getSubMenuWrapper(android.view.SubMenu frameworkSubMenu) {
-        if (frameworkSubMenu != null) {
-            // Instantiate HashMap if null
+    final SubMenu getSubMenuWrapper(final SubMenu subMenu) {
+        if (subMenu instanceof SupportSubMenu) {
+            final SupportSubMenu supportSubMenu = (SupportSubMenu) subMenu;
+
+            // Instantiate Map if null
             if (mSubMenus == null) {
-                mSubMenus = new HashMap<android.view.SubMenu, SubMenu>();
+                mSubMenus = new ArrayMap<>();
             }
 
-            SubMenu compatSubMenu = mSubMenus.get(frameworkSubMenu);
+            SubMenu wrappedMenu = mSubMenus.get(supportSubMenu);
 
-            if (null == compatSubMenu) {
-                compatSubMenu = MenuWrapperFactory.createSupportSubMenuWrapper(frameworkSubMenu);
-                mSubMenus.put(frameworkSubMenu, compatSubMenu);
+            if (null == wrappedMenu) {
+                wrappedMenu = MenuWrapperFactory.wrapSupportSubMenu(mContext, supportSubMenu);
+                mSubMenus.put(supportSubMenu, wrappedMenu);
             }
-            return compatSubMenu;
+            return wrappedMenu;
         }
-        return null;
+        return subMenu;
     }
 
 
@@ -85,7 +96,7 @@
             return;
         }
 
-        Iterator<android.view.MenuItem> iterator = mMenuItems.keySet().iterator();
+        Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
         android.view.MenuItem menuItem;
 
         while (iterator.hasNext()) {
@@ -101,7 +112,7 @@
             return;
         }
 
-        Iterator<android.view.MenuItem> iterator = mMenuItems.keySet().iterator();
+        Iterator<SupportMenuItem> iterator = mMenuItems.keySet().iterator();
         android.view.MenuItem menuItem;
 
         while (iterator.hasNext()) {
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
index 09029d7..a2e9783 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ActionProvider;
 import android.support.v4.internal.view.SupportMenuItem;
@@ -384,7 +385,17 @@
 
     @Override
     public CharSequence getTitleCondensed() {
-        return mTitleCondensed != null ? mTitleCondensed : mTitle;
+        final CharSequence ctitle = mTitleCondensed != null ? mTitleCondensed : mTitle;
+
+        if (Build.VERSION.SDK_INT < 18 && ctitle != null && !(ctitle instanceof String)) {
+            // For devices pre-JB-MR2, where we have a non-String CharSequence, we need to
+            // convert this to a String so that EventLog.writeEvent() does not throw an exception
+            // in Activity.onMenuItemSelected()
+            return ctitle.toString();
+        } else {
+            // Else, we just return the condensed title
+            return ctitle;
+        }
     }
 
     @Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
index a84e34c..3e6a99a 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperICS.java
@@ -16,8 +16,11 @@
 
 package android.support.v7.internal.view.menu;
 
+import android.annotation.TargetApi;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.view.ActionProvider;
 import android.support.v4.view.MenuItemCompat;
@@ -32,26 +35,18 @@
 import java.lang.reflect.Method;
 
 /**
+ * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
  * @hide
  */
-public class MenuItemWrapperICS extends BaseMenuWrapper<android.view.MenuItem> implements SupportMenuItem {
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class MenuItemWrapperICS extends BaseMenuWrapper<SupportMenuItem> implements MenuItem {
     static final String LOG_TAG = "MenuItemWrapper";
 
-    private final boolean mEmulateProviderVisibilityOverride;
-    // Tracks the last requested visibility
-    private boolean mLastRequestVisible;
-
     // Reflection Method to call setExclusiveCheckable
     private Method mSetExclusiveCheckableMethod;
 
-    MenuItemWrapperICS(android.view.MenuItem object, boolean emulateProviderVisibilityOverride) {
-        super(object);
-        mLastRequestVisible = object.isVisible();
-        mEmulateProviderVisibilityOverride = emulateProviderVisibilityOverride;
-    }
-
-    MenuItemWrapperICS(android.view.MenuItem object) {
-        this(object, true);
+    MenuItemWrapperICS(Context context, SupportMenuItem object) {
+        super(context, object);
     }
 
     @Override
@@ -177,14 +172,7 @@
 
     @Override
     public MenuItem setVisible(boolean visible) {
-        if (mEmulateProviderVisibilityOverride) {
-            mLastRequestVisible = visible;
-            // If we need to be visible, we need to check whether the ActionProvider overrides it
-            if (checkActionProviderOverrideVisibility()) {
-                return this;
-            }
-        }
-        return wrappedSetVisible(visible);
+        return mWrappedObject.setVisible(visible);
     }
 
     @Override
@@ -238,7 +226,7 @@
 
     @Override
     public MenuItem setActionView(View view) {
-        if (view instanceof CollapsibleActionView) {
+        if (view instanceof android.view.CollapsibleActionView) {
             view = new CollapsibleActionViewWrapper(view);
         }
         mWrappedObject.setActionView(view);
@@ -251,7 +239,7 @@
         mWrappedObject.setActionView(resId);
 
         View actionView = mWrappedObject.getActionView();
-        if (actionView instanceof CollapsibleActionView) {
+        if (actionView instanceof android.view.CollapsibleActionView) {
             // If the inflated Action View is support-collapsible, wrap it
             mWrappedObject.setActionView(new CollapsibleActionViewWrapper(actionView));
         }
@@ -269,16 +257,18 @@
 
     @Override
     public MenuItem setActionProvider(android.view.ActionProvider provider) {
-        mWrappedObject.setActionProvider(provider);
-        if (provider != null && mEmulateProviderVisibilityOverride) {
-            checkActionProviderOverrideVisibility();
-        }
+        mWrappedObject.setSupportActionProvider(
+                provider != null ? createActionProviderWrapper(provider) : null);
         return this;
     }
 
     @Override
     public android.view.ActionProvider getActionProvider() {
-        return mWrappedObject.getActionProvider();
+        ActionProvider provider = mWrappedObject.getSupportActionProvider();
+        if (provider instanceof ActionProviderWrapper) {
+            return ((ActionProviderWrapper) provider).mInner;
+        }
+        return null;
     }
 
     @Override
@@ -298,32 +288,11 @@
 
     @Override
     public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
-        mWrappedObject.setOnActionExpandListener(listener);
-        return this;
-    }
-
-    @Override
-    public SupportMenuItem setSupportOnActionExpandListener(
-            MenuItemCompat.OnActionExpandListener listener) {
-        mWrappedObject.setOnActionExpandListener(listener != null ?
+        mWrappedObject.setSupportOnActionExpandListener(listener != null ?
                 new OnActionExpandListenerWrapper(listener) : null);
-        return null;
-    }
-
-    @Override
-    public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
-        mWrappedObject.setActionProvider(actionProvider != null ?
-                createActionProviderWrapper(actionProvider) : null);
         return this;
     }
 
-    @Override
-    public ActionProvider getSupportActionProvider() {
-        ActionProviderWrapper providerWrapper =
-                (ActionProviderWrapper) mWrappedObject.getActionProvider();
-        return providerWrapper != null ? providerWrapper.mInner : null;
-    }
-
     public void setExclusiveCheckable(boolean checkable) {
         try {
             if (mSetExclusiveCheckableMethod == null) {
@@ -336,26 +305,8 @@
         }
     }
 
-    ActionProviderWrapper createActionProviderWrapper(ActionProvider provider) {
-        return new ActionProviderWrapper(provider);
-    }
-
-    /**
-     * @return true if the ActionProvider has overriden the visibility
-     */
-    final boolean checkActionProviderOverrideVisibility() {
-        if (mLastRequestVisible) {
-            ActionProvider provider = getSupportActionProvider();
-            if (provider != null && provider.overridesItemVisibility() && !provider.isVisible()) {
-                wrappedSetVisible(false);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    final MenuItem wrappedSetVisible(boolean visible) {
-        return mWrappedObject.setVisible(visible);
+    ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
+        return new ActionProviderWrapper(mContext, provider);
     }
 
     private class OnMenuItemClickListenerWrapper extends BaseWrapper<OnMenuItemClickListener>
@@ -371,10 +322,10 @@
         }
     }
 
-    private class OnActionExpandListenerWrapper extends BaseWrapper<MenuItemCompat.OnActionExpandListener>
-            implements android.view.MenuItem.OnActionExpandListener {
+    private class OnActionExpandListenerWrapper extends BaseWrapper<MenuItem.OnActionExpandListener>
+            implements MenuItemCompat.OnActionExpandListener {
 
-        OnActionExpandListenerWrapper(MenuItemCompat.OnActionExpandListener object) {
+        OnActionExpandListenerWrapper(MenuItem.OnActionExpandListener object) {
             super(object);
         }
 
@@ -389,32 +340,16 @@
         }
     }
 
-    class ActionProviderWrapper extends android.view.ActionProvider {
-        final ActionProvider mInner;
+    class ActionProviderWrapper extends android.support.v4.view.ActionProvider {
+        final android.view.ActionProvider mInner;
 
-        public ActionProviderWrapper(ActionProvider inner) {
-            super(inner.getContext());
+        public ActionProviderWrapper(Context context, android.view.ActionProvider inner) {
+            super(context);
             mInner = inner;
-
-            if (mEmulateProviderVisibilityOverride) {
-                mInner.setVisibilityListener(new ActionProvider.VisibilityListener() {
-                    @Override
-                    public void onActionProviderVisibilityChanged(boolean isVisible) {
-                        if (mInner.overridesItemVisibility() && mLastRequestVisible) {
-                            wrappedSetVisible(isVisible);
-                        }
-                    }
-                });
-            }
         }
 
         @Override
         public View onCreateActionView() {
-            if (mEmulateProviderVisibilityOverride) {
-                // This is a convenient place to hook in and check if we need to override the
-                // visibility after being created.
-                checkActionProviderOverrideVisibility();
-            }
             return mInner.onCreateActionView();
         }
 
@@ -434,13 +369,18 @@
         }
     }
 
+    /**
+     * Wrap a support {@link android.support.v7.view.CollapsibleActionView} into a framework
+     * {@link android.view.CollapsibleActionView}.
+     */
     static class CollapsibleActionViewWrapper extends FrameLayout
-            implements android.view.CollapsibleActionView {
-        final CollapsibleActionView mWrappedView;
+            implements CollapsibleActionView {
+
+        final android.view.CollapsibleActionView mWrappedView;
 
         CollapsibleActionViewWrapper(View actionView) {
             super(actionView.getContext());
-            mWrappedView = (CollapsibleActionView) actionView;
+            mWrappedView = (android.view.CollapsibleActionView) actionView;
             addView(actionView);
         }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
index 48fea03..4dbb0e0 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemWrapperJB.java
@@ -16,27 +16,36 @@
 
 package android.support.v7.internal.view.menu;
 
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.view.ActionProvider;
 import android.view.MenuItem;
 import android.view.View;
 
+/**
+ * Wraps a support {@link SupportMenuItem} as a framework {@link android.view.MenuItem}
+ * @hide
+ */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
 class MenuItemWrapperJB extends MenuItemWrapperICS {
-    MenuItemWrapperJB(android.view.MenuItem object) {
-        // We do not want to use the emulation of Action Provider visibility override
-        super(object, false);
+
+    MenuItemWrapperJB(Context context, SupportMenuItem object) {
+        super(context, object);
     }
 
     @Override
-    ActionProviderWrapper createActionProviderWrapper(ActionProvider provider) {
-        return new ActionProviderWrapperJB(provider);
+    ActionProviderWrapper createActionProviderWrapper(android.view.ActionProvider provider) {
+        return new ActionProviderWrapperJB(mContext, provider);
     }
 
     class ActionProviderWrapperJB extends ActionProviderWrapper
-            implements ActionProvider.VisibilityListener {
-        android.view.ActionProvider.VisibilityListener mListener;
+            implements android.view.ActionProvider.VisibilityListener {
+        ActionProvider.VisibilityListener mListener;
 
-        public ActionProviderWrapperJB(ActionProvider inner) {
-            super(inner);
+        public ActionProviderWrapperJB(Context context, android.view.ActionProvider inner) {
+            super(context, inner);
         }
 
         @Override
@@ -60,8 +69,7 @@
         }
 
         @Override
-        public void setVisibilityListener(
-                android.view.ActionProvider.VisibilityListener listener) {
+        public void setVisibilityListener(ActionProvider.VisibilityListener listener) {
             mListener = listener;
             mInner.setVisibilityListener(listener != null ? this : null);
         }
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
index 10c096b..af7deef 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
@@ -57,6 +57,7 @@
     private final boolean mOverflowOnly;
     private final int mPopupMaxWidth;
     private final int mPopupStyleAttr;
+    private final int mPopupStyleRes;
 
     private View mAnchorView;
     private ListPopupWindow mPopup;
@@ -85,12 +86,18 @@
 
     public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
             boolean overflowOnly, int popupStyleAttr) {
+        this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
+    }
+
+    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
+            boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
         mContext = context;
         mInflater = LayoutInflater.from(context);
         mMenu = menu;
         mAdapter = new MenuAdapter(mMenu);
         mOverflowOnly = overflowOnly;
         mPopupStyleAttr = popupStyleAttr;
+        mPopupStyleRes = popupStyleRes;
 
         final Resources res = context.getResources();
         mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
@@ -125,7 +132,7 @@
     }
 
     public boolean tryShow() {
-        mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr);
+        mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
         mPopup.setOnDismissListener(this);
         mPopup.setOnItemClickListener(this);
         mPopup.setAdapter(mAdapter);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
index 10e583e..74358747 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperFactory.java
@@ -16,12 +16,14 @@
 
 package android.support.v7.internal.view.menu;
 
+import android.content.Context;
 import android.os.Build;
 import android.support.v4.internal.view.SupportMenu;
 import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.internal.view.SupportSubMenu;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.SubMenu;
 
 /**
  * @hide
@@ -30,43 +32,25 @@
     private MenuWrapperFactory() {
     }
 
-    public static Menu createMenuWrapper(android.view.Menu frameworkMenu) {
+    public static Menu wrapSupportMenu(Context context, SupportMenu supportMenu) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new MenuWrapperICS(frameworkMenu);
-        }
-        return frameworkMenu;
-    }
-
-    public static MenuItem createMenuItemWrapper(android.view.MenuItem frameworkMenuItem) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            return new MenuItemWrapperJB(frameworkMenuItem);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new MenuItemWrapperICS(frameworkMenuItem);
-        }
-        return frameworkMenuItem;
-    }
-
-    public static SupportMenu createSupportMenuWrapper(android.view.Menu frameworkMenu) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new MenuWrapperICS(frameworkMenu);
+            return new MenuWrapperICS(context, supportMenu);
         }
         throw new UnsupportedOperationException();
     }
 
-    public static SupportSubMenu createSupportSubMenuWrapper(
-            android.view.SubMenu frameworkSubMenu) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new SubMenuWrapperICS(frameworkSubMenu);
+    public static MenuItem wrapSupportMenuItem(Context context, SupportMenuItem supportMenuItem) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            return new MenuItemWrapperJB(context, supportMenuItem);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            return new MenuItemWrapperICS(context, supportMenuItem);
         }
         throw new UnsupportedOperationException();
     }
 
-    public static SupportMenuItem createSupportMenuItemWrapper(
-            android.view.MenuItem frameworkMenuItem) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            return new MenuItemWrapperJB(frameworkMenuItem);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new MenuItemWrapperICS(frameworkMenuItem);
+    public static SubMenu wrapSupportSubMenu(Context context, SupportSubMenu supportSubMenu) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            return new SubMenuWrapperICS(context, supportSubMenu);
         }
         throw new UnsupportedOperationException();
     }
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
index 8d0e30f..79833f5 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuWrapperICS.java
@@ -17,17 +17,23 @@
 package android.support.v7.internal.view.menu;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.support.v4.internal.view.SupportMenu;
 import android.support.v4.internal.view.SupportMenuItem;
 import android.view.KeyEvent;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 
-class MenuWrapperICS extends BaseMenuWrapper<android.view.Menu> implements SupportMenu {
+/**
+ * Wraps a support {@link SupportMenu} as a framework {@link android.view.Menu}
+ * @hide
+ */
+class MenuWrapperICS extends BaseMenuWrapper<SupportMenu> implements Menu {
 
-    MenuWrapperICS(android.view.Menu object) {
-        super(object);
+    MenuWrapperICS(Context context, SupportMenu object) {
+        super(context, object);
     }
 
     @Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
index 3e70e49..a4306e9 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuWrapperICS.java
@@ -16,71 +16,77 @@
 
 package android.support.v7.internal.view.menu;
 
+import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.support.v4.internal.view.SupportSubMenu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 
-class SubMenuWrapperICS extends MenuWrapperICS implements SupportSubMenu {
-    SubMenuWrapperICS(android.view.SubMenu subMenu) {
-        super(subMenu);
+/**
+ * Wraps a support {@link SupportSubMenu} as a framework {@link android.view.SubMenu}
+ * @hide
+ */
+class SubMenuWrapperICS extends MenuWrapperICS implements SubMenu {
+
+    SubMenuWrapperICS(Context context, SupportSubMenu subMenu) {
+        super(context, subMenu);
     }
 
     @Override
-    public android.view.SubMenu getWrappedObject() {
-        return (android.view.SubMenu) mWrappedObject;
+    public SupportSubMenu getWrappedObject() {
+        return (SupportSubMenu) mWrappedObject;
     }
 
     @Override
     public SubMenu setHeaderTitle(int titleRes) {
-        ((android.view.SubMenu) mWrappedObject).setHeaderTitle(titleRes);
+        getWrappedObject().setHeaderTitle(titleRes);
         return this;
     }
 
     @Override
     public SubMenu setHeaderTitle(CharSequence title) {
-        ((android.view.SubMenu) mWrappedObject).setHeaderTitle(title);
+        getWrappedObject().setHeaderTitle(title);
         return this;
     }
 
     @Override
     public SubMenu setHeaderIcon(int iconRes) {
-        ((android.view.SubMenu) mWrappedObject).setHeaderIcon(iconRes);
+        getWrappedObject().setHeaderIcon(iconRes);
         return this;
     }
 
     @Override
     public SubMenu setHeaderIcon(Drawable icon) {
-        ((android.view.SubMenu) mWrappedObject).setHeaderIcon(icon);
+        getWrappedObject().setHeaderIcon(icon);
         return this;
     }
 
     @Override
     public SubMenu setHeaderView(View view) {
-        ((android.view.SubMenu) mWrappedObject).setHeaderView(view);
+        getWrappedObject().setHeaderView(view);
         return this;
     }
 
     @Override
     public void clearHeader() {
-        ((android.view.SubMenu) mWrappedObject).clearHeader();
+        getWrappedObject().clearHeader();
     }
 
     @Override
     public SubMenu setIcon(int iconRes) {
-        ((android.view.SubMenu) mWrappedObject).setIcon(iconRes);
+        getWrappedObject().setIcon(iconRes);
         return this;
     }
 
     @Override
     public SubMenu setIcon(Drawable icon) {
-        ((android.view.SubMenu) mWrappedObject).setIcon(icon);
+        getWrappedObject().setIcon(icon);
         return this;
     }
 
     @Override
     public MenuItem getItem() {
-        return getMenuItemWrapper(((android.view.SubMenu) mWrappedObject).getItem());
+        return getMenuItemWrapper(getWrappedObject().getItem());
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
index 9d9673d..1332b8b 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
@@ -31,7 +31,6 @@
 import android.support.v4.widget.ScrollerCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.VersionUtils;
-import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.support.v7.widget.Toolbar;
 import android.util.AttributeSet;
@@ -319,12 +318,10 @@
         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
         final Rect systemInsets = insets;
 
-        // The top and bottom action bars are always within the content area.
-        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
-        if (mActionBarBottom != null) {
-            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
-        }
+        // Since we're not the top level view in the window decor, we do not need to
+        // inset the Action Bars
 
+        boolean changed = false;
         mBaseInnerInsets.set(systemInsets);
         ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets);
         if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
@@ -668,7 +665,7 @@
     }
 
     @Override
-    public void setWindowCallback(WindowCallback cb) {
+    public void setWindowCallback(Window.Callback cb) {
         pullChildren();
         mDecorToolbar.setWindowCallback(cb);
     }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
index acc30ee..9931afb 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
@@ -25,6 +25,7 @@
 import android.database.DataSetObserver;
 import android.graphics.drawable.Drawable;
 import android.support.v4.view.ActionProvider;
+import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.widget.LinearLayoutCompat;
 import android.support.v7.widget.ListPopupWindow;
@@ -38,7 +39,6 @@
 import android.widget.BaseAdapter;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
@@ -68,6 +68,8 @@
 public class ActivityChooserView extends ViewGroup implements
         ActivityChooserModel.ActivityChooserModelClient {
 
+    private static final String LOG_TAG = "ActivityChooserView";
+
     /**
      * An adapter for displaying the activities in an {@link android.widget.AdapterView}.
      */
@@ -181,13 +183,13 @@
      */
     private int mDefaultActionButtonContentDescription;
 
-        /**
-         * Create a new instance.
-         *
-         * @param context The application environment.
-         */
-        public ActivityChooserView(Context context) {
-            this(context, null);
+    /**
+     * Create a new instance.
+     *
+     * @param context The application environment.
+     */
+    public ActivityChooserView(Context context) {
+        this(context, null);
     }
 
     /**
@@ -235,10 +237,29 @@
         mDefaultActivityButton.setOnLongClickListener(mCallbacks);
         mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.image);
 
-        mExpandActivityOverflowButton = (FrameLayout) findViewById(R.id.expand_activities_button);
-        mExpandActivityOverflowButton.setOnClickListener(mCallbacks);
+        final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
+        expandButton.setOnClickListener(mCallbacks);
+        expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
+            @Override
+            public ListPopupWindow getPopup() {
+                return getListPopupWindow();
+            }
+
+            @Override
+            protected boolean onForwardingStarted() {
+                showPopup();
+                return true;
+            }
+
+            @Override
+            protected boolean onForwardingStopped() {
+                dismissPopup();
+                return true;
+            }
+        });
+        mExpandActivityOverflowButton = expandButton;
         mExpandActivityOverflowButtonImage =
-                (ImageView) mExpandActivityOverflowButton.findViewById(R.id.image);
+            (ImageView) expandButton.findViewById(R.id.image);
         mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
 
         mAdapter = new ActivityChooserViewAdapter();
@@ -723,9 +744,9 @@
                     titleView.setText(activity.loadLabel(packageManager));
                     // Highlight the default.
                     if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
-                        //TODO convertView.setActivated(true);
+                        ViewCompat.setActivated(convertView, true);
                     } else {
-                        //TODO convertView.setActivated(false);
+                        ViewCompat.setActivated(convertView, false);
                     }
                     return convertView;
                 default:
@@ -783,10 +804,6 @@
             return mDataModel.getHistorySize();
         }
 
-        public int getMaxActivityCount() {
-            return mMaxActivityCount;
-        }
-
         public ActivityChooserModel getDataModel() {
             return mDataModel;
         }
@@ -805,4 +822,22 @@
             return mShowDefaultActivity;
         }
     }
+
+    /**
+     * Allows us to set the background using TintTypedArray
+     * @hide
+     */
+    public static class InnerLayout extends LinearLayoutCompat {
+
+        private static final int[] TINT_ATTRS = {
+                android.R.attr.background
+        };
+
+        public InnerLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS);
+            setBackgroundDrawable(a.getDrawable(0));
+            a.recycle();
+        }
+    }
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java b/v7/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java
index 028570b..511a332 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/AppCompatPopupWindow.java
@@ -18,19 +18,24 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
 import android.widget.PopupWindow;
 
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
 /**
  * @hide
  */
 public class AppCompatPopupWindow extends PopupWindow {
 
+    private static final String TAG = "AppCompatPopupWindow";
+
     private final boolean mOverlapAnchor;
 
     public AppCompatPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -42,6 +47,12 @@
         // We re-set this for tinting purposes
         setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground));
         a.recycle();
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            // For devices pre-ICS, we need to wrap the internal OnScrollChangedListener
+            // due to NPEs.
+            wrapOnScrollChangedListener(this);
+        }
     }
 
     @Override
@@ -71,4 +82,39 @@
         }
         super.update(anchor, xoff, yoff, width, height);
     }
+
+    private static void wrapOnScrollChangedListener(final PopupWindow popup) {
+        try {
+            final Field fieldAnchor = PopupWindow.class.getDeclaredField("mAnchor");
+            fieldAnchor.setAccessible(true);
+
+            final Field fieldListener = PopupWindow.class
+                    .getDeclaredField("mOnScrollChangedListener");
+            fieldListener.setAccessible(true);
+
+            final OnScrollChangedListener originalListener =
+                    (OnScrollChangedListener) fieldListener.get(popup);
+
+            // Now set a new listener, wrapping the original and only proxying the call when
+            // we have an anchor view.
+            fieldListener.set(popup, new OnScrollChangedListener() {
+                @Override
+                public void onScrollChanged() {
+                    try {
+                        WeakReference<View> mAnchor = (WeakReference<View>) fieldAnchor.get(popup);
+                        if (mAnchor == null || mAnchor.get() == null) {
+                            return;
+                        } else {
+                            originalListener.onScrollChanged();
+                        }
+                    } catch (IllegalAccessException e) {
+                        // Oh well...
+                    }
+                }
+            });
+        } catch (Exception e) {
+            Log.d(TAG, "Exception while installing workaround OnScrollChangedListener", e);
+        }
+    }
+
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/CompatTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/CompatTextView.java
index 50f50fc..f90ab75 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/CompatTextView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/CompatTextView.java
@@ -44,18 +44,41 @@
     public CompatTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        boolean allCaps = false;
+        // First read the TextAppearance style id
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView,
+                defStyle, 0);
+        final int ap = a.getResourceId(R.styleable.CompatTextView_android_textAppearance, -1);
+        a.recycle();
 
-        TypedArray style = context
-                .obtainStyledAttributes(attrs, R.styleable.CompatTextView, defStyle, 0);
-        allCaps = style.getBoolean(R.styleable.CompatTextView_textAllCaps, false);
-        style.recycle();
-
-        // Framework impl also checks TextAppearance for textAllCaps. This isn't needed for our
-        // purposes so has been omitted.
-
-        if (allCaps) {
-            setTransformationMethod(new AllCapsTransformationMethod(context));
+        // Now check TextAppearance's textAllCaps value
+        if (ap != -1) {
+            TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance);
+            if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+                setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
+            }
+            appearance.recycle();
         }
+
+        // Now read the style's value
+        a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView, defStyle, 0);
+        if (a.hasValue(R.styleable.CompatTextView_textAllCaps)) {
+            setAllCaps(a.getBoolean(R.styleable.CompatTextView_textAllCaps, false));
+        }
+        a.recycle();
+    }
+
+    public void setAllCaps(boolean allCaps) {
+        setTransformationMethod(allCaps ? new AllCapsTransformationMethod(getContext()) : null);
+    }
+
+    @Override
+    public void setTextAppearance(Context context, int resid) {
+        super.setTextAppearance(context, resid);
+
+        TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance);
+        if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+            setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
+        }
+        appearance.recycle();
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java
index e218efd..e4400ac 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java
@@ -19,10 +19,10 @@
 
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
-import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.util.SparseArray;
 import android.view.Menu;
+import android.view.Window;
 
 /**
  * Implemented by the top-level decor layout for a window. DecorContentParent offers
@@ -31,7 +31,7 @@
  * @hide
  */
 public interface DecorContentParent {
-    void setWindowCallback(WindowCallback cb);
+    void setWindowCallback(Window.Callback cb);
     void setWindowTitle(CharSequence title);
     CharSequence getTitle();
     void initFeature(int windowFeature);
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
index 67422d5d..c601e64 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
@@ -20,12 +20,13 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
-import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.util.SparseArray;
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.Window;
 import android.widget.SpinnerAdapter;
 
 /**
@@ -41,7 +42,7 @@
     boolean isSplit();
     boolean hasExpandedActionView();
     void collapseActionView();
-    void setWindowCallback(WindowCallback cb);
+    void setWindowCallback(Window.Callback cb);
     void setWindowTitle(CharSequence title);
     CharSequence getTitle();
     void setTitle(CharSequence title);
@@ -92,4 +93,12 @@
     void setDefaultNavigationIcon(Drawable icon);
     void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
     void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
+    void setBackgroundDrawable(Drawable d);
+    int getHeight();
+    void setVisibility(int visible);
+    int getVisibility();
+    void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
+            MenuBuilder.Callback menuBuilderCallback);
+    Menu getMenu();
+    int getPopupTheme();
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java b/v7/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java
new file mode 100644
index 0000000..1ff5e4d
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DrawableUtils.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * @hide
+ */
+public class DrawableUtils {
+
+    private static final String TAG = "DrawableUtils";
+
+    public static final Rect INSETS_NONE = new Rect();
+
+    private static Class<?> sInsetsClazz;
+
+    static {
+        if (Build.VERSION.SDK_INT >= 18) {
+            try {
+                sInsetsClazz = Class.forName("android.graphics.Insets");
+            } catch (ClassNotFoundException e) {
+                // Oh well...
+            }
+        }
+    }
+
+    private DrawableUtils() {}
+
+    /**
+     * Allows us to get the optical insets for a {@link Drawable}. Since this is hidden we need to
+     * use reflection. Since the {@code Insets} class is hidden also, we return a Rect instead.
+     */
+    public static Rect getOpticalBounds(Drawable drawable) {
+        if (sInsetsClazz != null) {
+            try {
+                // If the Drawable is wrapped, we need to manually unwrap it and process
+                // the wrapped drawable.
+                drawable = DrawableCompat.unwrap(drawable);
+
+                final Method getOpticalInsetsMethod = drawable.getClass()
+                        .getMethod("getOpticalInsets");
+                final Object insets = getOpticalInsetsMethod.invoke(drawable);
+
+                if (insets != null) {
+                    // If the drawable has some optical insets, let's copy them into a Rect
+                    final Rect result = new Rect();
+
+                    for (Field field : sInsetsClazz.getFields()) {
+                        switch (field.getName()) {
+                            case "left":
+                               result.left = field.getInt(insets);
+                                break;
+                            case "top":
+                                result.top = field.getInt(insets);
+                                break;
+                            case "right":
+                                result.right = field.getInt(insets);
+                                break;
+                            case "bottom":
+                                result.bottom = field.getInt(insets);
+                                break;
+                        }
+                    }
+                    return result;
+                }
+            } catch (Exception e) {
+                // Eugh, we hit some kind of reflection issue...
+                Log.e(TAG, "Couldn't obtain the optical insets. Ignoring.");
+            }
+        }
+
+        // If we reach here, either we're running on a device pre-v18, the Drawable didn't have
+        // any optical insets, or a reflection issue, so we'll just return an empty rect
+        return INSETS_NONE;
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
index c75d2ce..e2e6c4c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.graphics.drawable.DrawableWrapper;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -74,11 +75,14 @@
 
     @Override
     public void setSelector(Drawable sel) {
-        mSelector = new GateKeeperDrawable(sel);
+        mSelector = sel != null ? new GateKeeperDrawable(sel) : null;
         super.setSelector(mSelector);
 
-        Rect padding = new Rect();
-        sel.getPadding(padding);
+        final Rect padding = new Rect();
+        if (sel != null) {
+            sel.getPadding(padding);
+        }
+
         mSelectionLeftPadding = padding.left;
         mSelectionTopPadding = padding.top;
         mSelectionRightPadding = padding.right;
@@ -88,7 +92,8 @@
     @Override
     protected void drawableStateChanged() {
         super.drawableStateChanged();
-        mSelector.setEnabled(true);
+
+        setSelectorEnabled(true);
         updateSelectorStateCompat();
     }
 
@@ -120,8 +125,10 @@
     protected void drawSelectorCompat(Canvas canvas) {
         if (!mSelectorRect.isEmpty()) {
             final Drawable selector = getSelector();
-            selector.setBounds(mSelectorRect);
-            selector.draw(canvas);
+            if (selector != null) {
+                selector.setBounds(mSelectorRect);
+                selector.draw(canvas);
+            }
         }
     }
 
@@ -322,7 +329,9 @@
     }
 
     protected void setSelectorEnabled(boolean enabled) {
-        mSelector.setEnabled(enabled);
+        if (mSelector != null) {
+            mSelector.setEnabled(enabled);
+        }
     }
 
     private static class GateKeeperDrawable extends DrawableWrapper {
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
deleted file mode 100644
index 3d83367..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
+++ /dev/null
@@ -1,923 +0,0 @@
-package android.support.v7.internal.widget;
-
-/*
- * Copyright (C) 2013 Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.graphics.drawable.shapes.Shape;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.Transformation;
-
-/**
- * @hide
- */
-public class ProgressBarCompat extends View {
-
-    private static final int MAX_LEVEL = 10000;
-    private static final int ANIMATION_RESOLUTION = 200;
-
-    /**
-     * android.R.styleable.ProgressBar is internalised, so we need to create it ourselves.
-      */
-    private static final int[] android_R_styleable_ProgressBar = new int[]{
-            android.R.attr.max,
-            android.R.attr.progress,
-            android.R.attr.secondaryProgress,
-            android.R.attr.indeterminate,
-            android.R.attr.indeterminateOnly,
-            android.R.attr.indeterminateDrawable,
-            android.R.attr.progressDrawable,
-            android.R.attr.indeterminateDuration,
-            android.R.attr.indeterminateBehavior,
-            android.R.attr.minWidth,
-            android.R.attr.maxWidth,
-            android.R.attr.minHeight,
-            android.R.attr.maxHeight,
-            android.R.attr.interpolator,
-    };
-
-    int mMinWidth;
-    int mMaxWidth;
-    int mMinHeight;
-    int mMaxHeight;
-
-    private int mProgress;
-    private int mSecondaryProgress;
-    private int mMax;
-
-    private int mBehavior;
-    private int mDuration;
-    private boolean mIndeterminate;
-    private boolean mOnlyIndeterminate;
-    private Transformation mTransformation;
-    private AlphaAnimation mAnimation;
-    private Drawable mIndeterminateDrawable;
-    private Drawable mProgressDrawable;
-    private Drawable mCurrentDrawable;
-    Bitmap mSampleTile;
-    private boolean mNoInvalidate;
-    private Interpolator mInterpolator;
-    private RefreshProgressRunnable mRefreshProgressRunnable;
-    private long mUiThreadId;
-    private boolean mShouldStartAnimationDrawable;
-    private long mLastDrawTime;
-
-    private boolean mInDrawing;
-
-    /**
-     * @hide
-     */
-    public ProgressBarCompat(Context context, AttributeSet attrs, int defStyle, int styleRes) {
-        super(context, attrs, defStyle);
-        mUiThreadId = Thread.currentThread().getId();
-        initProgressBar();
-
-        TypedArray a = context.obtainStyledAttributes(attrs, android_R_styleable_ProgressBar,
-                defStyle, styleRes);
-
-        mNoInvalidate = true;
-
-        setMax(a.getInt(0, mMax));
-        setProgress(a.getInt(1, mProgress));
-        setSecondaryProgress(a.getInt(2, mSecondaryProgress));
-
-        final boolean indeterminate = a.getBoolean(3, mIndeterminate);
-        mOnlyIndeterminate = a.getBoolean(4, mOnlyIndeterminate);
-
-        Drawable drawable = a.getDrawable(5);
-        if (drawable != null) {
-            drawable = tileifyIndeterminate(drawable);
-            setIndeterminateDrawable(drawable);
-        }
-
-        drawable = a.getDrawable(6);
-        if (drawable != null) {
-            drawable = tileify(drawable, false);
-            // Calling this method can set mMaxHeight, make sure the corresponding
-            // XML attribute for mMaxHeight is read after calling this method
-            setProgressDrawable(drawable);
-        }
-
-        mDuration = a.getInt(7, mDuration);
-        mBehavior = a.getInt(8, mBehavior);
-        mMinWidth = a.getDimensionPixelSize(9, mMinWidth);
-        mMaxWidth = a.getDimensionPixelSize(10, mMaxWidth);
-        mMinHeight = a.getDimensionPixelSize(11, mMinHeight);
-        mMaxHeight = a.getDimensionPixelSize(12, mMaxHeight);
-
-        final int resID = a.getResourceId(13, android.R.anim.linear_interpolator);
-        if (resID > 0) {
-            setInterpolator(context, resID);
-        }
-
-        a.recycle();
-
-        mNoInvalidate = false;
-        setIndeterminate(mOnlyIndeterminate || indeterminate);
-    }
-
-    /**
-     * Converts a drawable to a tiled version of itself. It will recursively
-     * traverse layer and state list drawables.
-     */
-    private Drawable tileify(Drawable drawable, boolean clip) {
-
-        if (drawable instanceof LayerDrawable) {
-            LayerDrawable background = (LayerDrawable) drawable;
-            final int N = background.getNumberOfLayers();
-            Drawable[] outDrawables = new Drawable[N];
-
-            for (int i = 0; i < N; i++) {
-                int id = background.getId(i);
-                outDrawables[i] = tileify(background.getDrawable(i),
-                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
-            }
-
-            LayerDrawable newBg = new LayerDrawable(outDrawables);
-
-            for (int i = 0; i < N; i++) {
-                newBg.setId(i, background.getId(i));
-            }
-
-            return newBg;
-
-        } else if (drawable instanceof BitmapDrawable) {
-            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
-            if (mSampleTile == null) {
-                mSampleTile = tileBitmap;
-            }
-
-            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
-
-            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
-                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
-            shapeDrawable.getPaint().setShader(bitmapShader);
-
-            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
-                    ClipDrawable.HORIZONTAL) : shapeDrawable;
-        }
-
-        return drawable;
-    }
-
-    Shape getDrawableShape() {
-        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
-        return new RoundRectShape(roundedCorners, null, null);
-    }
-
-    /**
-     * Convert a AnimationDrawable for use as a barberpole animation.
-     * Each frame of the animation is wrapped in a ClipDrawable and
-     * given a tiling BitmapShader.
-     */
-    private Drawable tileifyIndeterminate(Drawable drawable) {
-        if (drawable instanceof AnimationDrawable) {
-            AnimationDrawable background = (AnimationDrawable) drawable;
-            final int N = background.getNumberOfFrames();
-            AnimationDrawable newBg = new AnimationDrawable();
-            newBg.setOneShot(background.isOneShot());
-
-            for (int i = 0; i < N; i++) {
-                Drawable frame = tileify(background.getFrame(i), true);
-                frame.setLevel(10000);
-                newBg.addFrame(frame, background.getDuration(i));
-            }
-            newBg.setLevel(10000);
-            drawable = newBg;
-        }
-        return drawable;
-    }
-
-    /**
-     * <p>
-     * Initialize the progress bar's default values:
-     * </p>
-     * <ul>
-     * <li>progress = 0</li>
-     * <li>max = 100</li>
-     * <li>animation duration = 4000 ms</li>
-     * <li>indeterminate = false</li>
-     * <li>behavior = repeat</li>
-     * </ul>
-     */
-    private void initProgressBar() {
-        mMax = 100;
-        mProgress = 0;
-        mSecondaryProgress = 0;
-        mIndeterminate = false;
-        mOnlyIndeterminate = false;
-        mDuration = 4000;
-        mBehavior = AlphaAnimation.RESTART;
-        mMinWidth = 24;
-        mMaxWidth = 48;
-        mMinHeight = 24;
-        mMaxHeight = 48;
-    }
-
-    /**
-     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
-     *
-     * @return true if the progress bar is in indeterminate mode
-     */
-    public synchronized boolean isIndeterminate() {
-        return mIndeterminate;
-    }
-
-    /**
-     * <p>Change the indeterminate mode for this progress bar. In indeterminate
-     * mode, the progress is ignored and the progress bar shows an infinite
-     * animation instead.</p>
-     *
-     * If this progress bar's style only supports indeterminate mode (such as the circular
-     * progress bars), then this will be ignored.
-     *
-     * @param indeterminate true to enable the indeterminate mode
-     */
-    public synchronized void setIndeterminate(boolean indeterminate) {
-        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
-            mIndeterminate = indeterminate;
-
-            if (indeterminate) {
-                // swap between indeterminate and regular backgrounds
-                mCurrentDrawable = mIndeterminateDrawable;
-                startAnimation();
-            } else {
-                mCurrentDrawable = mProgressDrawable;
-                stopAnimation();
-            }
-        }
-    }
-
-    /**
-     * <p>Get the drawable used to draw the progress bar in
-     * indeterminate mode.</p>
-     *
-     * @return a {@link android.graphics.drawable.Drawable} instance
-     *
-     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
-     * @see #setIndeterminate(boolean)
-     */
-    public Drawable getIndeterminateDrawable() {
-        return mIndeterminateDrawable;
-    }
-
-    /**
-     * <p>Define the drawable used to draw the progress bar in
-     * indeterminate mode.</p>
-     *
-     * @param d the new drawable
-     *
-     * @see #getIndeterminateDrawable()
-     * @see #setIndeterminate(boolean)
-     */
-    public void setIndeterminateDrawable(Drawable d) {
-        if (d != null) {
-            d.setCallback(this);
-        }
-        mIndeterminateDrawable = d;
-        if (mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
-        }
-    }
-
-    /**
-     * <p>Get the drawable used to draw the progress bar in
-     * progress mode.</p>
-     *
-     * @return a {@link android.graphics.drawable.Drawable} instance
-     *
-     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
-     * @see #setIndeterminate(boolean)
-     */
-    public Drawable getProgressDrawable() {
-        return mProgressDrawable;
-    }
-
-    /**
-     * <p>Define the drawable used to draw the progress bar in
-     * progress mode.</p>
-     *
-     * @param d the new drawable
-     *
-     * @see #getProgressDrawable()
-     * @see #setIndeterminate(boolean)
-     */
-    public void setProgressDrawable(Drawable d) {
-        boolean needUpdate;
-        if (mProgressDrawable != null && d != mProgressDrawable) {
-            mProgressDrawable.setCallback(null);
-            needUpdate = true;
-        } else {
-            needUpdate = false;
-        }
-
-        if (d != null) {
-            d.setCallback(this);
-
-            // Make sure the android_R_styleable_ProgressBar is always tall enough
-            int drawableHeight = d.getMinimumHeight();
-            if (mMaxHeight < drawableHeight) {
-                mMaxHeight = drawableHeight;
-                requestLayout();
-            }
-        }
-        mProgressDrawable = d;
-        if (!mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
-        }
-
-        if (needUpdate) {
-            updateDrawableBounds(getWidth(), getHeight());
-            updateDrawableState();
-            doRefreshProgress(android.R.id.progress, mProgress, false, false);
-            doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return who == mProgressDrawable || who == mIndeterminateDrawable
-                || super.verifyDrawable(who);
-    }
-
-    @Override
-    public void postInvalidate() {
-        if (!mNoInvalidate) {
-            super.postInvalidate();
-        }
-    }
-
-    private class RefreshProgressRunnable implements Runnable {
-
-        private int mId;
-        private int mProgress;
-        private boolean mFromUser;
-
-        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
-        }
-
-        public void run() {
-            doRefreshProgress(mId, mProgress, mFromUser, true);
-            // Put ourselves back in the cache when we are done
-            mRefreshProgressRunnable = this;
-        }
-
-        public void setup(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
-        }
-
-    }
-
-    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
-            boolean callBackToApp) {
-        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
-        final Drawable d = mCurrentDrawable;
-        if (d != null) {
-            Drawable progressDrawable = null;
-
-            if (d instanceof LayerDrawable) {
-                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
-            }
-
-            final int level = (int) (scale * MAX_LEVEL);
-            (progressDrawable != null ? progressDrawable : d).setLevel(level);
-        } else {
-            invalidate();
-        }
-    }
-
-    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
-        if (mUiThreadId == Thread.currentThread().getId()) {
-            doRefreshProgress(id, progress, fromUser, true);
-        } else {
-            RefreshProgressRunnable r;
-            if (mRefreshProgressRunnable != null) {
-                // Use cached RefreshProgressRunnable if available
-                r = mRefreshProgressRunnable;
-                // Uncache it
-                mRefreshProgressRunnable = null;
-                r.setup(id, progress, fromUser);
-            } else {
-                // Make a new one
-                r = new RefreshProgressRunnable(id, progress, fromUser);
-            }
-            post(r);
-        }
-    }
-
-    /**
-     * <p>Set the current progress to the specified value. Does not do anything
-     * if the progress bar is in indeterminate mode.</p>
-     *
-     * @param progress the new progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #getProgress()
-     * @see #incrementProgressBy(int)
-     */
-    public synchronized void setProgress(int progress) {
-        setProgress(progress, false);
-    }
-
-    synchronized void setProgress(int progress, boolean fromUser) {
-        if (mIndeterminate) {
-            return;
-        }
-
-        if (progress < 0) {
-            progress = 0;
-        }
-
-        if (progress > mMax) {
-            progress = mMax;
-        }
-
-        if (progress != mProgress) {
-            mProgress = progress;
-            refreshProgress(android.R.id.progress, mProgress, fromUser);
-        }
-    }
-
-    /**
-     * <p>
-     * Set the current secondary progress to the specified value. Does not do
-     * anything if the progress bar is in indeterminate mode.
-     * </p>
-     *
-     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #getSecondaryProgress()
-     * @see #incrementSecondaryProgressBy(int)
-     */
-    public synchronized void setSecondaryProgress(int secondaryProgress) {
-        if (mIndeterminate) {
-            return;
-        }
-
-        if (secondaryProgress < 0) {
-            secondaryProgress = 0;
-        }
-
-        if (secondaryProgress > mMax) {
-            secondaryProgress = mMax;
-        }
-
-        if (secondaryProgress != mSecondaryProgress) {
-            mSecondaryProgress = secondaryProgress;
-            refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
-        }
-    }
-
-    /**
-     * <p>Get the progress bar's current level of progress. Return 0 when the
-     * progress bar is in indeterminate mode.</p>
-     *
-     * @return the current progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #setProgress(int)
-     * @see #setMax(int)
-     * @see #getMax()
-     */
-    public synchronized int getProgress() {
-        return mIndeterminate ? 0 : mProgress;
-    }
-
-    /**
-     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
-     * progress bar is in indeterminate mode.</p>
-     *
-     * @return the current secondary progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #setSecondaryProgress(int)
-     * @see #setMax(int)
-     * @see #getMax()
-     */
-    public synchronized int getSecondaryProgress() {
-        return mIndeterminate ? 0 : mSecondaryProgress;
-    }
-
-    /**
-     * <p>Return the upper limit of this progress bar's range.</p>
-     *
-     * @return a positive integer
-     *
-     * @see #setMax(int)
-     * @see #getProgress()
-     * @see #getSecondaryProgress()
-     */
-    public synchronized int getMax() {
-        return mMax;
-    }
-
-    /**
-     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
-     *
-     * @param max the upper range of this progress bar
-     *
-     * @see #getMax()
-     * @see #setProgress(int)
-     * @see #setSecondaryProgress(int)
-     */
-    public synchronized void setMax(int max) {
-        if (max < 0) {
-            max = 0;
-        }
-        if (max != mMax) {
-            mMax = max;
-            postInvalidate();
-
-            if (mProgress > max) {
-                mProgress = max;
-            }
-            refreshProgress(android.R.id.progress, mProgress, false);
-        }
-    }
-
-    /**
-     * <p>Increase the progress bar's progress by the specified amount.</p>
-     *
-     * @param diff the amount by which the progress must be increased
-     *
-     * @see #setProgress(int)
-     */
-    public synchronized final void incrementProgressBy(int diff) {
-        setProgress(mProgress + diff);
-    }
-
-    /**
-     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
-     *
-     * @param diff the amount by which the secondary progress must be increased
-     *
-     * @see #setSecondaryProgress(int)
-     */
-    public synchronized final void incrementSecondaryProgressBy(int diff) {
-        setSecondaryProgress(mSecondaryProgress + diff);
-    }
-
-    /**
-     * <p>Start the indeterminate progress animation.</p>
-     */
-    void startAnimation() {
-        if (getVisibility() != VISIBLE) {
-            return;
-        }
-
-        if (mIndeterminateDrawable instanceof Animatable) {
-            mShouldStartAnimationDrawable = true;
-            mAnimation = null;
-        } else {
-            if (mInterpolator == null) {
-                mInterpolator = new LinearInterpolator();
-            }
-
-            mTransformation = new Transformation();
-            mAnimation = new AlphaAnimation(0.0f, 1.0f);
-            mAnimation.setRepeatMode(mBehavior);
-            mAnimation.setRepeatCount(Animation.INFINITE);
-            mAnimation.setDuration(mDuration);
-            mAnimation.setInterpolator(mInterpolator);
-            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
-        }
-        postInvalidate();
-    }
-
-    /**
-     * <p>Stop the indeterminate progress animation.</p>
-     */
-    void stopAnimation() {
-        mAnimation = null;
-        mTransformation = null;
-        if (mIndeterminateDrawable instanceof Animatable) {
-            ((Animatable) mIndeterminateDrawable).stop();
-            mShouldStartAnimationDrawable = false;
-        }
-        postInvalidate();
-    }
-
-    /**
-     * Sets the acceleration curve for the indeterminate animation.
-     * The interpolator is loaded as a resource from the specified context.
-     *
-     * @param context The application environment
-     * @param resID The resource identifier of the interpolator to load
-     */
-    public void setInterpolator(Context context, int resID) {
-        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
-    }
-
-    /**
-     * Sets the acceleration curve for the indeterminate animation.
-     * Defaults to a linear interpolation.
-     *
-     * @param interpolator The interpolator which defines the acceleration curve
-     */
-    public void setInterpolator(Interpolator interpolator) {
-        mInterpolator = interpolator;
-    }
-
-    /**
-     * Gets the acceleration curve type for the indeterminate animation.
-     *
-     * @return the {@link Interpolator} associated to this animation
-     */
-    public Interpolator getInterpolator() {
-        return mInterpolator;
-    }
-
-    @Override
-    public void setVisibility(int v) {
-        if (getVisibility() != v) {
-            super.setVisibility(v);
-
-            if (mIndeterminate) {
-                // let's be nice with the UI thread
-                if (v == GONE || v == INVISIBLE) {
-                    stopAnimation();
-                } else {
-                    startAnimation();
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        if (Build.VERSION.SDK_INT >= 8) {
-            super.onVisibilityChanged(changedView, visibility);
-        }
-
-        if (mIndeterminate) {
-            // let's be nice with the UI thread
-            if (visibility == GONE || visibility == INVISIBLE) {
-                stopAnimation();
-            } else {
-                startAnimation();
-            }
-        }
-    }
-
-    @Override
-    public void invalidateDrawable(Drawable dr) {
-        if (!mInDrawing) {
-            if (verifyDrawable(dr)) {
-                final Rect dirty = dr.getBounds();
-                final int scrollX = getScrollX() + getPaddingLeft();
-                final int scrollY = getScrollY() + getPaddingTop();
-
-                invalidate(dirty.left + scrollX, dirty.top + scrollY,
-                        dirty.right + scrollX, dirty.bottom + scrollY);
-            } else {
-                super.invalidateDrawable(dr);
-            }
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        updateDrawableBounds(w, h);
-    }
-
-    private void updateDrawableBounds(int w, int h) {
-        // onDraw will translate the canvas so we draw starting at 0,0
-        int right = w - getPaddingRight() - getPaddingLeft();
-        int bottom = h - getPaddingBottom() - getPaddingTop();
-        int top = 0;
-        int left = 0;
-
-        if (mIndeterminateDrawable != null) {
-            // Aspect ratio logic does not apply to AnimationDrawables
-            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
-                // Maintain aspect ratio. Certain kinds of animated drawables
-                // get very confused otherwise.
-                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
-                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
-                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
-                final float boundAspect = (float) w / h;
-                if (intrinsicAspect != boundAspect) {
-                    if (boundAspect > intrinsicAspect) {
-                        // New width is larger. Make it smaller to match height.
-                        final int width = (int) (h * intrinsicAspect);
-                        left = (w - width) / 2;
-                        right = left + width;
-                    } else {
-                        // New height is larger. Make it smaller to match width.
-                        final int height = (int) (w * (1 / intrinsicAspect));
-                        top = (h - height) / 2;
-                        bottom = top + height;
-                    }
-                }
-            }
-            mIndeterminateDrawable.setBounds(left, top, right, bottom);
-        }
-
-        if (mProgressDrawable != null) {
-            mProgressDrawable.setBounds(0, 0, right, bottom);
-        }
-    }
-
-    @Override
-    protected synchronized void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        Drawable d = mCurrentDrawable;
-        if (d != null) {
-            // Translate canvas so a indeterminate circular progress bar with padding
-            // rotates properly in its animation
-            canvas.save();
-            canvas.translate(getPaddingLeft(), getPaddingTop());
-            long time = getDrawingTime();
-            if (mAnimation != null) {
-                mAnimation.getTransformation(time, mTransformation);
-                float scale = mTransformation.getAlpha();
-                try {
-                    mInDrawing = true;
-                    d.setLevel((int) (scale * MAX_LEVEL));
-                } finally {
-                    mInDrawing = false;
-                }
-                if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
-                    mLastDrawTime = SystemClock.uptimeMillis();
-                    postInvalidateDelayed(ANIMATION_RESOLUTION);
-                }
-            }
-            d.draw(canvas);
-            canvas.restore();
-            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
-                ((Animatable) d).start();
-                mShouldStartAnimationDrawable = false;
-            }
-        }
-    }
-
-    @Override
-    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Drawable d = mCurrentDrawable;
-
-        int dw = 0;
-        int dh = 0;
-        if (d != null) {
-            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
-            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
-        }
-        updateDrawableState();
-        dw += getPaddingLeft() + getPaddingRight();
-        dh += getPaddingTop() + getPaddingBottom();
-
-        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
-                resolveSize(dh, heightMeasureSpec));
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        updateDrawableState();
-    }
-
-    private void updateDrawableState() {
-        int[] state = getDrawableState();
-
-        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
-            mProgressDrawable.setState(state);
-        }
-
-        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
-            mIndeterminateDrawable.setState(state);
-        }
-    }
-
-    static class SavedState extends BaseSavedState {
-        int progress;
-        int secondaryProgress;
-
-        /**
-         * Constructor called from {@link ProgressBarCompat#onSaveInstanceState()}
-         */
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        /**
-         * Constructor called from {@link #CREATOR}
-         */
-        private SavedState(Parcel in) {
-            super(in);
-            progress = in.readInt();
-            secondaryProgress = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(progress);
-            out.writeInt(secondaryProgress);
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        // Force our ancestor class to save its state
-        Parcelable superState = super.onSaveInstanceState();
-        SavedState ss = new SavedState(superState);
-
-        ss.progress = mProgress;
-        ss.secondaryProgress = mSecondaryProgress;
-
-        return ss;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        setProgress(ss.progress);
-        setSecondaryProgress(ss.secondaryProgress);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mIndeterminate) {
-            startAnimation();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mIndeterminate) {
-            stopAnimation();
-        }
-        if(mRefreshProgressRunnable != null) {
-            removeCallbacks(mRefreshProgressRunnable);
-        }
-
-        // This should come after stopAnimation(), otherwise an invalidate message remains in the
-        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
-        super.onDetachedFromWindow();
-    }
-
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java
new file mode 100644
index 0000000..48ebab8
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ResourcesWrapper.java
@@ -0,0 +1,282 @@
+/*
+ * 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.v7.internal.widget;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.ConstantState;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.LongSparseArray;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This extends Resources but delegates the calls to another Resources object. This enables
+ * any customization done by some subclass of Resources to be also picked up.
+ */
+class ResourcesWrapper extends Resources {
+
+    private final Resources mResources;
+
+    public ResourcesWrapper(Resources resources) {
+        super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+        mResources = resources;
+    }
+
+    @Override
+    public CharSequence getText(int id) throws NotFoundException {
+        return mResources.getText(id);
+    }
+
+    @Override
+    public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+        return mResources.getQuantityText(id, quantity);
+    }
+
+    @Override
+    public String getString(int id) throws NotFoundException {
+        return mResources.getString(id);
+    }
+
+    @Override
+    public String getString(int id, Object... formatArgs) throws NotFoundException {
+        return mResources.getString(id, formatArgs);
+    }
+
+    @Override
+    public String getQuantityString(int id, int quantity, Object... formatArgs)
+            throws NotFoundException {
+        return mResources.getQuantityString(id, quantity, formatArgs);
+    }
+
+    @Override
+    public String getQuantityString(int id, int quantity) throws NotFoundException {
+        return mResources.getQuantityString(id, quantity);
+    }
+
+    @Override
+    public CharSequence getText(int id, CharSequence def) {
+        return mResources.getText(id, def);
+    }
+
+    @Override
+    public CharSequence[] getTextArray(int id) throws NotFoundException {
+        return mResources.getTextArray(id);
+    }
+
+    @Override
+    public String[] getStringArray(int id) throws NotFoundException {
+        return mResources.getStringArray(id);
+    }
+
+    @Override
+    public int[] getIntArray(int id) throws NotFoundException {
+        return mResources.getIntArray(id);
+    }
+
+    @Override
+    public TypedArray obtainTypedArray(int id) throws NotFoundException {
+        return mResources.obtainTypedArray(id);
+    }
+
+    @Override
+    public float getDimension(int id) throws NotFoundException {
+        return mResources.getDimension(id);
+    }
+
+    @Override
+    public int getDimensionPixelOffset(int id) throws NotFoundException {
+        return mResources.getDimensionPixelOffset(id);
+    }
+
+    @Override
+    public int getDimensionPixelSize(int id) throws NotFoundException {
+        return mResources.getDimensionPixelSize(id);
+    }
+
+    @Override
+    public float getFraction(int id, int base, int pbase) {
+        return mResources.getFraction(id, base, pbase);
+    }
+
+    @Override
+    public Drawable getDrawable(int id) throws NotFoundException {
+        return mResources.getDrawable(id);
+    }
+
+    @Override
+    public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
+        return mResources.getDrawable(id, theme);
+    }
+
+    @Override
+    public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+        return mResources.getDrawableForDensity(id, density);
+    }
+
+    @Override
+    public Drawable getDrawableForDensity(int id, int density, Theme theme) {
+        return mResources.getDrawableForDensity(id, density, theme);
+    }
+
+    @Override
+    public Movie getMovie(int id) throws NotFoundException {
+        return mResources.getMovie(id);
+    }
+
+    @Override
+    public int getColor(int id) throws NotFoundException {
+        return mResources.getColor(id);
+    }
+
+    @Override
+    public ColorStateList getColorStateList(int id) throws NotFoundException {
+        return mResources.getColorStateList(id);
+    }
+
+    @Override
+    public boolean getBoolean(int id) throws NotFoundException {
+        return mResources.getBoolean(id);
+    }
+
+    @Override
+    public int getInteger(int id) throws NotFoundException {
+        return mResources.getInteger(id);
+    }
+
+    @Override
+    public XmlResourceParser getLayout(int id) throws NotFoundException {
+        return mResources.getLayout(id);
+    }
+
+    @Override
+    public XmlResourceParser getAnimation(int id) throws NotFoundException {
+        return mResources.getAnimation(id);
+    }
+
+    @Override
+    public XmlResourceParser getXml(int id) throws NotFoundException {
+        return mResources.getXml(id);
+    }
+
+    @Override
+    public InputStream openRawResource(int id) throws NotFoundException {
+        return mResources.openRawResource(id);
+    }
+
+    @Override
+    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+        return mResources.openRawResource(id, value);
+    }
+
+    @Override
+    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+        return mResources.openRawResourceFd(id);
+    }
+
+    @Override
+    public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValue(id, outValue, resolveRefs);
+    }
+
+    @Override
+    public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValueForDensity(id, density, outValue, resolveRefs);
+    }
+
+    @Override
+    public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValue(name, outValue, resolveRefs);
+    }
+
+    @Override
+    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+        return mResources.obtainAttributes(set, attrs);
+    }
+
+    @Override
+    public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+        super.updateConfiguration(config, metrics);
+        if (mResources != null) { // called from super's constructor. So, need to check.
+            mResources.updateConfiguration(config, metrics);
+        }
+    }
+
+    @Override
+    public DisplayMetrics getDisplayMetrics() {
+        return mResources.getDisplayMetrics();
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return mResources.getConfiguration();
+    }
+
+    @Override
+    public int getIdentifier(String name, String defType, String defPackage) {
+        return mResources.getIdentifier(name, defType, defPackage);
+    }
+
+    @Override
+    public String getResourceName(int resid) throws NotFoundException {
+        return mResources.getResourceName(resid);
+    }
+
+    @Override
+    public String getResourcePackageName(int resid) throws NotFoundException {
+        return mResources.getResourcePackageName(resid);
+    }
+
+    @Override
+    public String getResourceTypeName(int resid) throws NotFoundException {
+        return mResources.getResourceTypeName(resid);
+    }
+
+    @Override
+    public String getResourceEntryName(int resid) throws NotFoundException {
+        return mResources.getResourceEntryName(resid);
+    }
+
+    @Override
+    public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+            throws XmlPullParserException, IOException {
+        mResources.parseBundleExtras(parser, outBundle);
+    }
+
+    @Override
+    public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
+            throws XmlPullParserException {
+        mResources.parseBundleExtra(tagName, attrs, outBundle);
+    }
+}
+
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java
index fb6ce4c..b2b8af2 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java
@@ -164,7 +164,9 @@
                 R.styleable.Spinner, defStyle, 0);
 
         // Need to reset this for tinting purposes
-        setBackgroundDrawable(a.getDrawable(R.styleable.Spinner_android_background));
+        if (a.hasValue(R.styleable.Spinner_android_background)) {
+            setBackgroundDrawable(a.getDrawable(R.styleable.Spinner_android_background));
+        }
 
         if (mode == MODE_THEME) {
             mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java b/v7/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java
new file mode 100644
index 0000000..aacecfa
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ThemeUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.v7.internal.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.v4.graphics.ColorUtils;
+import android.util.TypedValue;
+
+class ThemeUtils {
+
+    private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
+
+    private static final int[] DISABLED_STATE_SET = new int[]{-android.R.attr.state_enabled};
+    private static final int[] EMPTY_STATE_SET = new int[0];
+
+    private static final int[] TEMP_ARRAY = new int[1];
+
+    static ColorStateList createDisabledStateList(int textColor, int disabledTextColor) {
+        // Now create a new ColorStateList with the default color, and the new disabled
+        // color
+        final int[][] states = new int[2][];
+        final int[] colors = new int[2];
+        int i = 0;
+
+        // Disabled state
+        states[i] = DISABLED_STATE_SET;
+        colors[i] = disabledTextColor;
+        i++;
+
+        // Default state
+        states[i] = EMPTY_STATE_SET;
+        colors[i] = textColor;
+        i++;
+
+        return new ColorStateList(states, colors);
+    }
+
+    static int getThemeAttrColor(Context context, int attr) {
+        TEMP_ARRAY[0] = attr;
+        TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+        try {
+            return a.getColor(0, 0);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    static ColorStateList getThemeAttrColorStateList(Context context, int attr) {
+        TEMP_ARRAY[0] = attr;
+        TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+        try {
+            return a.getColorStateList(0);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    static int getDisabledThemeAttrColor(Context context, int attr) {
+        final ColorStateList csl = getThemeAttrColorStateList(context, attr);
+        if (csl != null && csl.isStateful()) {
+            // If the CSL is stateful, we'll assume it has a disabled state and use it
+            return csl.getColorForState(DISABLED_STATE_SET, csl.getDefaultColor());
+        } else {
+            // Else, we'll generate the color using disabledAlpha from the theme
+
+            final TypedValue tv = getTypedValue();
+            // Now retrieve the disabledAlpha value from the theme
+            context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
+            final float disabledAlpha = tv.getFloat();
+
+            return getThemeAttrColor(context, attr, disabledAlpha);
+        }
+    }
+
+    private static TypedValue getTypedValue() {
+        TypedValue typedValue = TL_TYPED_VALUE.get();
+        if (typedValue == null) {
+            typedValue = new TypedValue();
+            TL_TYPED_VALUE.set(typedValue);
+        }
+        return typedValue;
+    }
+
+    static int getThemeAttrColor(Context context, int attr, float alpha) {
+        final int color = getThemeAttrColor(context, attr);
+        final int originalAlpha = Color.alpha(color);
+        return ColorUtils.setAlphaComponent(color, Math.round(originalAlpha * alpha));
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
new file mode 100644
index 0000000..29ec605
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.annotation.Nullable;
+import android.support.v4.view.TintableBackgroundView;
+import android.util.AttributeSet;
+import android.widget.AutoCompleteTextView;
+
+/**
+ * An tint aware {@link android.widget.AutoCompleteTextView}.
+ * <p>
+ * This will automatically be used when you use {@link AutoCompleteTextView} in your layouts. You
+ * should only need to manually use this class writing custom views.
+ */
+public class TintAutoCompleteTextView extends AutoCompleteTextView implements
+        TintableBackgroundView {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background,
+            android.R.attr.popupBackground
+    };
+
+    private TintManager mTintManager;
+    private TintInfo mBackgroundTint;
+
+    public TintAutoCompleteTextView(Context context) {
+        this(context, null);
+    }
+
+    public TintAutoCompleteTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
+    }
+
+    public TintAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
+
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            mTintManager = a.getTintManager();
+
+            if (a.hasValue(0)) {
+                setSupportBackgroundTintList(
+                        mTintManager.getColorStateList(a.getResourceId(0, -1)));
+            }
+            if (a.hasValue(1)) {
+                setDropDownBackgroundDrawable(a.getDrawable(1));
+            }
+            a.recycle();
+        }
+    }
+
+    @Override
+    public void setDropDownBackgroundResource(int id) {
+        setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
+     * android.content.res.ColorStateList)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public ColorStateList getSupportBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintMode = tintMode;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public PorterDuff.Mode getSupportBackgroundTintMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        applySupportBackgroundTint();
+    }
+
+    private void applySupportBackgroundTint() {
+        if (getBackground() != null && mBackgroundTint != null) {
+            TintManager.tintViewBackground(this, mBackgroundTint);
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java b/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java
new file mode 100644
index 0000000..41b3c3b
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v4.view.TintableBackgroundView;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * An tint aware {@link android.widget.Button}.
+ * <p>
+ * This will automatically be used when you use {@link android.widget.Button} in your layouts. You
+ * should only need to manually use this class when writing custom views.
+ */
+public class TintButton extends Button implements TintableBackgroundView {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background
+    };
+
+    private TintInfo mBackgroundTint;
+
+    public TintButton(Context context) {
+        this(context, null);
+    }
+
+    public TintButton(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.buttonStyle);
+    }
+
+    public TintButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            if (a.hasValue(0)) {
+                setSupportBackgroundTintList(
+                        a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
+            }
+            a.recycle();
+        }
+
+        final ColorStateList textColors = getTextColors();
+        if (textColors != null && !textColors.isStateful()) {
+            // If we have a ColorStateList which isn't stateful, create one which includes
+            // a disabled state
+
+            final int disabledTextColor;
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                // Pre-Lollipop, we will use textColorSecondary with android:disabledAlpha
+                // applied
+                disabledTextColor = ThemeUtils.getDisabledThemeAttrColor(context,
+                        android.R.attr.textColorSecondary);
+            } else {
+                // With certain styles on Lollipop, there is a StateListAnimator which sets
+                // an alpha on the whole view, so we don't need to apply disabledAlpha to
+                // textColorSecondary
+                disabledTextColor = ThemeUtils.getThemeAttrColor(context,
+                        android.R.attr.textColorSecondary);
+            }
+
+            setTextColor(ThemeUtils.createDisabledStateList(
+                    textColors.getDefaultColor(), disabledTextColor));
+        }
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
+     * android.content.res.ColorStateList)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public ColorStateList getSupportBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintMode = tintMode;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public PorterDuff.Mode getSupportBackgroundTintMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        applySupportBackgroundTint();
+    }
+
+    private void applySupportBackgroundTint() {
+        if (getBackground() != null && mBackgroundTint != null) {
+            TintManager.tintViewBackground(this, mBackgroundTint);
+        }
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java b/v7/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java
index 37b0409..ea1fa68 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintCheckBox.java
@@ -22,8 +22,9 @@
 
 /**
  * An tint aware {@link android.widget.CheckBox}.
- *
- * @hide
+ * <p>
+ * This will automatically be used when you use {@link android.widget.CheckBox} in your layouts.
+ * You should only need to manually use this class when writing custom views.
  */
 public class TintCheckBox extends CheckBox {
 
@@ -31,7 +32,7 @@
             android.R.attr.button
     };
 
-    private final TintManager mTintManager;
+    private TintManager mTintManager;
 
     public TintCheckBox(Context context) {
         this(context, null);
@@ -44,16 +45,22 @@
     public TintCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
-                defStyleAttr, 0);
-        setButtonDrawable(a.getDrawable(0));
-        a.recycle();
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            setButtonDrawable(a.getDrawable(0));
+            a.recycle();
 
-        mTintManager = a.getTintManager();
+            mTintManager = a.getTintManager();
+        }
     }
 
     @Override
     public void setButtonDrawable(int resid) {
-        setButtonDrawable(mTintManager.getDrawable(resid));
+        if (mTintManager != null) {
+            setButtonDrawable(mTintManager.getDrawable(resid));
+        } else {
+            super.setButtonDrawable(resid);
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java
index 57aa126..c969ad2 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintCheckedTextView.java
@@ -19,12 +19,12 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.CheckedTextView;
-import android.widget.RadioButton;
 
 /**
  * An tint aware {@link android.widget.CheckedTextView}.
- *
- * @hide
+ * <p>
+ * This will automatically be used when you use {@link android.widget.CheckedTextView} in your
+ * layouts. You should only need to manually use this class when writing custom views.
  */
 public class TintCheckedTextView extends CheckedTextView {
 
@@ -32,7 +32,7 @@
             android.R.attr.checkMark
     };
 
-    private final TintManager mTintManager;
+    private TintManager mTintManager;
 
     public TintCheckedTextView(Context context) {
         this(context, null);
@@ -45,17 +45,23 @@
     public TintCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
-                defStyleAttr, 0);
-        setCheckMarkDrawable(a.getDrawable(0));
-        a.recycle();
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            setCheckMarkDrawable(a.getDrawable(0));
+            a.recycle();
 
-        mTintManager = a.getTintManager();
+            mTintManager = a.getTintManager();
+        }
     }
 
     @Override
     public void setCheckMarkDrawable(int resid) {
-        setCheckMarkDrawable(mTintManager.getDrawable(resid));
+        if (mTintManager != null) {
+            setCheckMarkDrawable(mTintManager.getDrawable(resid));
+        } else {
+            super.setCheckMarkDrawable(resid);
+        }
     }
 
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java
new file mode 100644
index 0000000..7947288
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintContextWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.v7.internal.widget;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+
+/**
+ * A {@link android.content.ContextWrapper} which returns a tint-aware
+ * {@link android.content.res.Resources} instance from {@link #getResources()}.
+ *
+ * @hide
+ */
+class TintContextWrapper extends ContextWrapper {
+
+    private final TintManager mTintManager;
+
+    public static Context wrap(Context context) {
+        if (!(context instanceof TintContextWrapper)) {
+            context = new TintContextWrapper(context);
+        }
+        return context;
+    }
+
+    TintContextWrapper(Context base) {
+        super(base);
+        mTintManager = new TintManager(base);
+    }
+
+    @Override
+    public Resources getResources() {
+        return mTintManager.getResources();
+    }
+
+    final TintManager getTintManager() {
+        return mTintManager;
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintDrawableWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/TintDrawableWrapper.java
deleted file mode 100644
index 41a4c42..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintDrawableWrapper.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.internal.widget;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
-
-/**
- * A {@link DrawableWrapper} which updates it's color filter using a {@link ColorStateList}.
- */
-class TintDrawableWrapper extends DrawableWrapper {
-
-    private final ColorStateList mTintStateList;
-    private final PorterDuff.Mode mTintMode;
-
-    private int mCurrentColor;
-
-    public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList) {
-        this(drawable, tintStateList, TintManager.DEFAULT_MODE);
-    }
-
-    public TintDrawableWrapper(Drawable drawable, ColorStateList tintStateList,
-            PorterDuff.Mode tintMode) {
-        super(drawable);
-        mTintStateList = tintStateList;
-        mTintMode = tintMode;
-    }
-
-    @Override
-    public boolean isStateful() {
-        return (mTintStateList != null && mTintStateList.isStateful()) || super.isStateful();
-    }
-
-    @Override
-    public boolean setState(int[] stateSet) {
-        boolean handled = super.setState(stateSet);
-        handled = updateTint(stateSet) || handled;
-        return handled;
-    }
-
-    private boolean updateTint(int[] state) {
-        if (mTintStateList != null) {
-            final int color = mTintStateList.getColorForState(state, mCurrentColor);
-            if (color != mCurrentColor) {
-                if (color != Color.TRANSPARENT) {
-                    setColorFilter(color, mTintMode);
-                } else {
-                    clearColorFilter();
-                }
-                mCurrentColor = color;
-                return true;
-            }
-        }
-        return false;
-    }
-
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintEditText.java b/v7/appcompat/src/android/support/v7/internal/widget/TintEditText.java
index bb53da3..95ba859 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintEditText.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintEditText.java
@@ -17,35 +17,113 @@
 package android.support.v7.internal.widget;
 
 import android.content.Context;
-import android.os.Build;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.annotation.Nullable;
+import android.support.v4.view.TintableBackgroundView;
 import android.util.AttributeSet;
 import android.widget.EditText;
 
 /**
  * An tint aware {@link android.widget.EditText}.
- *
- * @hide
+ * <p>
+ * This will automatically be used when you use {@link android.widget.EditText} in your
+ * layouts. You should only need to manually use this class when writing custom views.
  */
-public class TintEditText extends EditText {
+public class TintEditText extends EditText implements TintableBackgroundView {
 
     private static final int[] TINT_ATTRS = {
             android.R.attr.background
     };
 
+    private TintInfo mBackgroundTint;
+
     public TintEditText(Context context) {
         this(context, null);
     }
 
     public TintEditText(Context context, AttributeSet attrs) {
-        this(context, attrs, android.R.attr.editTextStyle);
+        this(TintContextWrapper.wrap(context), attrs, android.R.attr.editTextStyle);
     }
 
     public TintEditText(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
-                defStyleAttr, 0);
-        setBackgroundDrawable(a.getDrawable(0));
-        a.recycle();
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            if (a.hasValue(0)) {
+                setSupportBackgroundTintList(
+                        a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
+            }
+            a.recycle();
+        }
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
+     * android.content.res.ColorStateList)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public ColorStateList getSupportBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintMode = tintMode;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public PorterDuff.Mode getSupportBackgroundTintMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        applySupportBackgroundTint();
+    }
+
+    private void applySupportBackgroundTint() {
+        if (getBackground() != null && mBackgroundTint != null) {
+            TintManager.tintViewBackground(this, mBackgroundTint);
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintImageView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintImageView.java
index 0d9df4c..17dd557 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintImageView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintImageView.java
@@ -46,7 +46,7 @@
     public TintImageView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs, TINT_ATTRS,
                 defStyleAttr, 0);
         if (a.length() > 0) {
             if (a.hasValue(0)) {
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v7/appcompat/src/android/support/v7/internal/widget/TintInfo.java
similarity index 63%
copy from v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
copy to v7/appcompat/src/android/support/v7/internal/widget/TintInfo.java
index 5ebd187..ba2a56a 100644
--- a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintInfo.java
@@ -14,17 +14,12 @@
  * limitations under the License.
  */
 
-package android.support.v4.view;
+package android.support.v7.internal.widget;
 
-import android.view.ViewGroup;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
 
-class ViewGroupCompatApi21 {
-
-    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
-        group.setTransitionGroup(isTransitionGroup);
-    }
-
-    public static boolean isTransitionGroup(ViewGroup group) {
-        return group.isTransitionGroup();
-    }
+class TintInfo {
+    ColorStateList mTintList;
+    PorterDuff.Mode mTintMode;
 }
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 bed1cdd..9fdfaa8 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
@@ -19,20 +19,29 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.util.LruCache;
 import android.support.v7.appcompat.R;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TypedValue;
+import android.view.View;
+
+import static android.support.v7.internal.widget.ThemeUtils.getDisabledThemeAttrColor;
+import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColor;
+import static android.support.v7.internal.widget.ThemeUtils.getThemeAttrColorStateList;
 
 /**
  * @hide
  */
-public class TintManager {
+public final class TintManager {
+
+    static final boolean SHOULD_BE_USED = Build.VERSION.SDK_INT < 21;
 
     private static final String TAG = TintManager.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -59,7 +68,8 @@
             R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
             R.drawable.abc_ic_voice_search_api_mtrl_alpha,
             R.drawable.abc_textfield_search_default_mtrl_alpha,
-            R.drawable.abc_textfield_default_mtrl_alpha
+            R.drawable.abc_textfield_default_mtrl_alpha,
+            R.drawable.abc_ab_share_pack_mtrl_alpha
     };
 
     /**
@@ -69,7 +79,8 @@
     private static final int[] TINT_COLOR_CONTROL_ACTIVATED = {
             R.drawable.abc_textfield_activated_mtrl_alpha,
             R.drawable.abc_textfield_search_activated_mtrl_alpha,
-            R.drawable.abc_cab_background_top_mtrl_alpha
+            R.drawable.abc_cab_background_top_mtrl_alpha,
+            R.drawable.abc_text_cursor_mtrl_alpha
     };
 
     /**
@@ -92,7 +103,13 @@
             R.drawable.abc_textfield_search_material,
             R.drawable.abc_spinner_mtrl_am_alpha,
             R.drawable.abc_btn_check_material,
-            R.drawable.abc_btn_radio_material
+            R.drawable.abc_btn_radio_material,
+            R.drawable.abc_spinner_textfield_background_material,
+            R.drawable.abc_ratingbar_full_material,
+            R.drawable.abc_switch_track_mtrl_alpha,
+            R.drawable.abc_switch_thumb_material,
+            R.drawable.abc_btn_default_mtrl_shape,
+            R.drawable.abc_btn_borderless_material
     };
 
     /**
@@ -107,9 +124,8 @@
     private final Resources mResources;
     private final TypedValue mTypedValue;
 
+    private final SparseArray<ColorStateList> mColorStateLists;
     private ColorStateList mDefaultColorStateList;
-    private ColorStateList mSwitchThumbStateList;
-    private ColorStateList mSwitchTrackStateList;
 
     /**
      * A helper method to instantiate a {@link TintManager} and then call {@link #getDrawable(int)}.
@@ -117,29 +133,44 @@
      */
     public static Drawable getDrawable(Context context, int resId) {
         if (isInTintList(resId)) {
-            return new TintManager(context).getDrawable(resId);
+            final TintManager tm = (context instanceof TintContextWrapper)
+                    ? ((TintContextWrapper) context).getTintManager()
+                    : new TintManager(context);
+            return tm.getDrawable(resId);
         } else {
             return ContextCompat.getDrawable(context, resId);
         }
     }
 
     public TintManager(Context context) {
+        mColorStateLists = new SparseArray<>();
         mContext = context;
-        mResources = new TintResources(context.getResources(), this);
         mTypedValue = new TypedValue();
+        mResources = new TintResources(context.getResources(), this);
+    }
+
+    Resources getResources() {
+        return mResources;
     }
 
     public Drawable getDrawable(int resId) {
         Drawable drawable = ContextCompat.getDrawable(mContext, resId);
 
         if (drawable != null) {
+            drawable = drawable.mutate();
+
             if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
-                drawable = new TintDrawableWrapper(drawable, getDefaultColorStateList());
-            } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
-                drawable = new TintDrawableWrapper(drawable, getSwitchTrackColorStateList());
-            } else if (resId == R.drawable.abc_switch_thumb_material) {
-                drawable = new TintDrawableWrapper(drawable, getSwitchThumbColorStateList(),
-                        PorterDuff.Mode.MULTIPLY);
+                ColorStateList colorStateList = getColorStateListForKnownDrawableId(resId);
+                PorterDuff.Mode tintMode = DEFAULT_MODE;
+                if (resId == R.drawable.abc_switch_thumb_material) {
+                    tintMode = PorterDuff.Mode.MULTIPLY;
+                }
+
+                if (colorStateList != null) {
+                    drawable = DrawableCompat.wrap(drawable);
+                    DrawableCompat.setTintList(drawable, colorStateList);
+                    DrawableCompat.setTintMode(drawable, tintMode);
+                }
             } else if (arrayContains(CONTAINERS_WITH_TINT_CHILDREN, resId)) {
                 drawable = mResources.getDrawable(resId);
             } else {
@@ -175,19 +206,9 @@
             if (tintMode == null) {
                 tintMode = DEFAULT_MODE;
             }
-            final int color = getThemeAttrColor(colorAttr);
+            final int color = getThemeAttrColor(mContext, colorAttr);
 
-            // First, lets see if the cache already contains the color filter
-            PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, tintMode);
-
-            if (filter == null) {
-                // Cache miss, so create a color filter and add it to the cache
-                filter = new PorterDuffColorFilter(color, tintMode);
-                COLOR_FILTER_CACHE.put(color, tintMode, filter);
-            }
-
-            // Finally set the color filter
-            drawable.setColorFilter(filter);
+            tintDrawableUsingColorFilter(drawable, color, tintMode);
 
             if (alpha != -1) {
                 drawable.setAlpha(alpha);
@@ -217,6 +238,41 @@
                 arrayContains(CONTAINERS_WITH_TINT_CHILDREN, drawableId);
     }
 
+    ColorStateList getColorStateList(int resId) {
+        return arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)
+                ? getColorStateListForKnownDrawableId(resId)
+                : null;
+    }
+
+    private ColorStateList getColorStateListForKnownDrawableId(int resId) {
+        // Try the cache first
+        ColorStateList colorStateList = mColorStateLists.get(resId);
+
+        if (colorStateList == null) {
+            // ...if the cache did not contain a color state list, try and create
+            if (resId == R.drawable.abc_edit_text_material) {
+                colorStateList = createEditTextColorStateList();
+            } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
+                colorStateList = createSwitchTrackColorStateList();
+            } else if (resId == R.drawable.abc_switch_thumb_material) {
+                colorStateList = createSwitchThumbColorStateList();
+            } else if (resId == R.drawable.abc_btn_default_mtrl_shape
+                    || resId == R.drawable.abc_btn_borderless_material) {
+                colorStateList = createButtonColorStateList();
+            } else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
+                    || resId == R.drawable.abc_spinner_textfield_background_material) {
+                colorStateList = createSpinnerColorStateList();
+            } else {
+                // If we don't have an explicit color state list for this Drawable, use the default
+                colorStateList = getDefaultColorStateList();
+            }
+
+            // ..and add it to the cache
+            mColorStateLists.append(resId, colorStateList);
+        }
+        return colorStateList;
+    }
+
     private ColorStateList getDefaultColorStateList() {
         if (mDefaultColorStateList == null) {
             /**
@@ -224,8 +280,9 @@
              * Order is important here. The default enabled state needs to go at the bottom.
              */
 
-            final int colorControlNormal = getThemeAttrColor(R.attr.colorControlNormal);
-            final int colorControlActivated = getThemeAttrColor(R.attr.colorControlActivated);
+            final int colorControlNormal = getThemeAttrColor(mContext, R.attr.colorControlNormal);
+            final int colorControlActivated = getThemeAttrColor(mContext,
+                    R.attr.colorControlActivated);
 
             final int[][] states = new int[7][];
             final int[] colors = new int[7];
@@ -233,7 +290,7 @@
 
             // Disabled state
             states[i] = new int[] { -android.R.attr.state_enabled };
-            colors[i] = getDisabledThemeAttrColor(R.attr.colorControlNormal);
+            colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
             i++;
 
             states[i] = new int[] { android.R.attr.state_focused };
@@ -266,82 +323,141 @@
         return mDefaultColorStateList;
     }
 
-    private ColorStateList getSwitchTrackColorStateList() {
-        if (mSwitchTrackStateList == null) {
-            final int[][] states = new int[3][];
-            final int[] colors = new int[3];
-            int i = 0;
+    private ColorStateList createSwitchTrackColorStateList() {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
+
+        // Disabled state
+        states[i] = new int[]{-android.R.attr.state_enabled};
+        colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.1f);
+        i++;
+
+        states[i] = new int[]{android.R.attr.state_checked};
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated, 0.3f);
+        i++;
+
+        // Default enabled state
+        states[i] = new int[0];
+        colors[i] = getThemeAttrColor(mContext, android.R.attr.colorForeground, 0.3f);
+        i++;
+
+        return new ColorStateList(states, colors);
+    }
+
+    private ColorStateList createSwitchThumbColorStateList() {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
+
+        final ColorStateList thumbColor = getThemeAttrColorStateList(mContext,
+                R.attr.colorSwitchThumbNormal);
+
+        if (thumbColor != null && thumbColor.isStateful()) {
+            // If colorSwitchThumbNormal is a valid ColorStateList, extract the default and
+            // disabled colors from it
 
             // Disabled state
-            states[i] = new int[] { -android.R.attr.state_enabled };
-            colors[i] = getThemeAttrColor(android.R.attr.colorForeground, 0.1f);
+            states[i] = new int[]{-android.R.attr.state_enabled};
+            colors[i] = thumbColor.getColorForState(states[i], 0);
             i++;
 
-            states[i] = new int[] { android.R.attr.state_checked };
-            colors[i] = getThemeAttrColor(R.attr.colorControlActivated, 0.3f);
+            states[i] = new int[]{android.R.attr.state_checked};
+            colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
             i++;
 
             // Default enabled state
             states[i] = new int[0];
-            colors[i] = getThemeAttrColor(android.R.attr.colorForeground, 0.3f);
+            colors[i] = thumbColor.getDefaultColor();
             i++;
-
-            mSwitchTrackStateList = new ColorStateList(states, colors);
-        }
-        return mSwitchTrackStateList;
-    }
-
-    private ColorStateList getSwitchThumbColorStateList() {
-        if (mSwitchThumbStateList == null) {
-            final int[][] states = new int[3][];
-            final int[] colors = new int[3];
-            int i = 0;
+        } else {
+            // Else we'll use an approximation using the default disabled alpha
 
             // Disabled state
-            states[i] = new int[] { -android.R.attr.state_enabled };
-            colors[i] = getDisabledThemeAttrColor(R.attr.colorSwitchThumbNormal);
+            states[i] = new int[]{-android.R.attr.state_enabled};
+            colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal);
             i++;
 
-            states[i] = new int[] { android.R.attr.state_checked };
-            colors[i] = getThemeAttrColor(R.attr.colorControlActivated);
+            states[i] = new int[]{android.R.attr.state_checked};
+            colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
             i++;
 
             // Default enabled state
             states[i] = new int[0];
-            colors[i] = getThemeAttrColor(R.attr.colorSwitchThumbNormal);
+            colors[i] = getThemeAttrColor(mContext, R.attr.colorSwitchThumbNormal);
             i++;
-
-            mSwitchThumbStateList = new ColorStateList(states, colors);
         }
-        return mSwitchThumbStateList;
+
+        return new ColorStateList(states, colors);
     }
 
-    int getThemeAttrColor(int attr) {
-        if (mContext.getTheme().resolveAttribute(attr, mTypedValue, true)) {
-            if (mTypedValue.type >= TypedValue.TYPE_FIRST_INT
-                    && mTypedValue.type <= TypedValue.TYPE_LAST_INT) {
-                return mTypedValue.data;
-            } else if (mTypedValue.type == TypedValue.TYPE_STRING) {
-                return mResources.getColor(mTypedValue.resourceId);
-            }
-        }
-        return 0;
+    private ColorStateList createEditTextColorStateList() {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
+
+        // Disabled state
+        states[i] = new int[]{-android.R.attr.state_enabled};
+        colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
+        i++;
+
+        states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused};
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal);
+        i++;
+
+        // Default enabled state
+        states[i] = new int[0];
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
+        i++;
+
+        return new ColorStateList(states, colors);
     }
 
-    int getThemeAttrColor(int attr, float alpha) {
-        final int color = getThemeAttrColor(attr);
-        final int originalAlpha = Color.alpha(color);
+    private ColorStateList createButtonColorStateList() {
+        final int[][] states = new int[4][];
+        final int[] colors = new int[4];
+        int i = 0;
 
-        // Return the color, multiplying the original alpha by the disabled value
-        return (color & 0x00ffffff) | (Math.round(originalAlpha * alpha) << 24);
+        // Disabled state
+        states[i] = new int[]{-android.R.attr.state_enabled};
+        colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorButtonNormal);
+        i++;
+
+        states[i] = new int[]{android.R.attr.state_pressed};
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight);
+        i++;
+
+        states[i] = new int[]{android.R.attr.state_focused};
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlHighlight);
+        i++;
+
+        // Default enabled state
+        states[i] = new int[0];
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorButtonNormal);
+        i++;
+
+        return new ColorStateList(states, colors);
     }
 
-    int getDisabledThemeAttrColor(int attr) {
-        // Now retrieve the disabledAlpha value from the theme
-        mContext.getTheme().resolveAttribute(android.R.attr.disabledAlpha, mTypedValue, true);
-        final float disabledAlpha = mTypedValue.getFloat();
+    private ColorStateList createSpinnerColorStateList() {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
 
-        return getThemeAttrColor(attr, disabledAlpha);
+        // Disabled state
+        states[i] = new int[]{-android.R.attr.state_enabled};
+        colors[i] = getDisabledThemeAttrColor(mContext, R.attr.colorControlNormal);
+        i++;
+
+        states[i] = new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused};
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlNormal);
+        i++;
+
+        states[i] = new int[0];
+        colors[i] = getThemeAttrColor(mContext, R.attr.colorControlActivated);
+        i++;
+
+        return new ColorStateList(states, colors);
     }
 
     private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
@@ -365,4 +481,31 @@
             return hashCode;
         }
     }
+
+    public static void tintViewBackground(View view, TintInfo tint) {
+        final Drawable background = view.getBackground();
+        if (tint.mTintList != null) {
+            tintDrawableUsingColorFilter(
+                    background,
+                    tint.mTintList.getColorForState(view.getDrawableState(),
+                            tint.mTintList.getDefaultColor()),
+                    tint.mTintMode != null ? tint.mTintMode : DEFAULT_MODE);
+        } else {
+            background.clearColorFilter();
+        }
+    }
+
+    private static void tintDrawableUsingColorFilter(Drawable drawable, int color,
+            PorterDuff.Mode mode) {
+        // First, lets see if the cache already contains the color filter
+        PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);
+
+        if (filter == null) {
+            // Cache miss, so create a color filter and add it to the cache
+            filter = new PorterDuffColorFilter(color, mode);
+            COLOR_FILTER_CACHE.put(color, mode, filter);
+        }
+
+        drawable.setColorFilter(filter);
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java
new file mode 100644
index 0000000..336243c
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.annotation.Nullable;
+import android.support.v4.view.TintableBackgroundView;
+import android.util.AttributeSet;
+import android.widget.MultiAutoCompleteTextView;
+
+/**
+ * An tint aware {@link android.widget.MultiAutoCompleteTextView}.
+ * <p>
+ * This will automatically be used when you use {@link android.widget.MultiAutoCompleteTextView}
+ * in your layouts. You should only need to manually use this class when writing custom views.
+ */
+public class TintMultiAutoCompleteTextView extends MultiAutoCompleteTextView
+        implements TintableBackgroundView {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background,
+            android.R.attr.popupBackground
+    };
+
+    private TintManager mTintManager;
+    private TintInfo mBackgroundTint;
+
+    public TintMultiAutoCompleteTextView(Context context) {
+        this(context, null);
+    }
+
+    public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.autoCompleteTextViewStyle);
+    }
+
+    public TintMultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
+
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            mTintManager = a.getTintManager();
+
+            if (a.hasValue(0)) {
+                setSupportBackgroundTintList(
+                        mTintManager.getColorStateList(a.getResourceId(0, -1)));
+            }
+            if (a.hasValue(1)) {
+                setDropDownBackgroundDrawable(a.getDrawable(1));
+            }
+            a.recycle();
+        }
+    }
+
+    @Override
+    public void setDropDownBackgroundResource(int id) {
+        if (mTintManager != null) {
+            setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+        } else {
+            super.setDropDownBackgroundResource(id);
+        }
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
+     * android.content.res.ColorStateList)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public ColorStateList getSupportBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintMode = tintMode;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public PorterDuff.Mode getSupportBackgroundTintMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        applySupportBackgroundTint();
+    }
+
+    private void applySupportBackgroundTint() {
+        if (getBackground() != null && mBackgroundTint != null) {
+            TintManager.tintViewBackground(this, mBackgroundTint);
+        }
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java b/v7/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java
index 6216063..f5e537c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintRadioButton.java
@@ -18,13 +18,13 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.widget.CheckBox;
 import android.widget.RadioButton;
 
 /**
  * An tint aware {@link android.widget.RadioButton}.
- *
- * @hide
+ * <p>
+ * This will automatically be used when you use {@link android.widget.RadioButton} in your
+ * layouts. You should only need to manually use this class when writing custom views.
  */
 public class TintRadioButton extends RadioButton {
 
@@ -32,7 +32,7 @@
             android.R.attr.button
     };
 
-    private final TintManager mTintManager;
+    private TintManager mTintManager;
 
     public TintRadioButton(Context context) {
         this(context, null);
@@ -45,16 +45,22 @@
     public TintRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
-                defStyleAttr, 0);
-        setButtonDrawable(a.getDrawable(0));
-        a.recycle();
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            setButtonDrawable(a.getDrawable(0));
+            a.recycle();
 
-        mTintManager = a.getTintManager();
+            mTintManager = a.getTintManager();
+        }
     }
 
     @Override
     public void setButtonDrawable(int resid) {
-        setButtonDrawable(mTintManager.getDrawable(resid));
+        if (mTintManager != null) {
+            setButtonDrawable(mTintManager.getDrawable(resid));
+        } else {
+            super.setButtonDrawable(resid);
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java b/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java
new file mode 100644
index 0000000..da0b965
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Shader;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.support.v4.graphics.drawable.DrawableWrapper;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.RatingBar;
+
+/**
+ * An tint aware {@link android.widget.RatingBar}.
+ * <p>
+ * This will automatically be used when you use {@link android.widget.RatingBar} in your
+ * layouts. You should only need to manually use this class when writing custom views.
+ */
+public class TintRatingBar extends RatingBar {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.indeterminateDrawable,
+            android.R.attr.progressDrawable
+    };
+
+    private Bitmap mSampleTile;
+
+    public TintRatingBar(Context context) {
+        this(context, null);
+    }
+
+    public TintRatingBar(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.ratingBarStyle);
+    }
+
+    public TintRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+
+            Drawable drawable = a.getDrawable(0);
+            if (drawable != null) {
+                setIndeterminateDrawable(tileifyIndeterminate(drawable));
+            }
+
+            drawable = a.getDrawable(1);
+            if (drawable != null) {
+                setProgressDrawable(tileify(drawable, false));
+            }
+
+            a.recycle();
+        }
+    }
+
+    /**
+     * Converts a drawable to a tiled version of itself. It will recursively
+     * traverse layer and state list drawables.
+     */
+    private Drawable tileify(Drawable drawable, boolean clip) {
+        if (drawable instanceof DrawableWrapper) {
+            Drawable inner = ((DrawableWrapper) drawable).getWrappedDrawable();
+            if (inner != null) {
+                inner = tileify(inner, clip);
+                ((DrawableWrapper) drawable).setWrappedDrawable(inner);
+            }
+        } else if (drawable instanceof LayerDrawable) {
+            LayerDrawable background = (LayerDrawable) drawable;
+            final int N = background.getNumberOfLayers();
+            Drawable[] outDrawables = new Drawable[N];
+
+            for (int i = 0; i < N; i++) {
+                int id = background.getId(i);
+                outDrawables[i] = tileify(background.getDrawable(i),
+                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
+            }
+            LayerDrawable newBg = new LayerDrawable(outDrawables);
+
+            for (int i = 0; i < N; i++) {
+                newBg.setId(i, background.getId(i));
+            }
+
+            return newBg;
+
+        } else if (drawable instanceof BitmapDrawable) {
+            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+            if (mSampleTile == null) {
+                mSampleTile = tileBitmap;
+            }
+
+            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+            shapeDrawable.getPaint().setShader(bitmapShader);
+            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+                    ClipDrawable.HORIZONTAL) : shapeDrawable;
+        }
+
+        return drawable;
+    }
+
+    /**
+     * Convert a AnimationDrawable for use as a barberpole animation.
+     * Each frame of the animation is wrapped in a ClipDrawable and
+     * given a tiling BitmapShader.
+     */
+    private Drawable tileifyIndeterminate(Drawable drawable) {
+        if (drawable instanceof AnimationDrawable) {
+            AnimationDrawable background = (AnimationDrawable) drawable;
+            final int N = background.getNumberOfFrames();
+            AnimationDrawable newBg = new AnimationDrawable();
+            newBg.setOneShot(background.isOneShot());
+
+            for (int i = 0; i < N; i++) {
+                Drawable frame = tileify(background.getFrame(i), true);
+                frame.setLevel(10000);
+                newBg.addFrame(frame, background.getDuration(i));
+            }
+            newBg.setLevel(10000);
+            drawable = newBg;
+        }
+        return drawable;
+    }
+
+    private Shape getDrawableShape() {
+        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+        return new RoundRectShape(roundedCorners, null, null);
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (mSampleTile != null) {
+            final int width = mSampleTile.getWidth() * getNumStars();
+            setMeasuredDimension(ViewCompat.resolveSizeAndState(width, widthMeasureSpec, 0),
+                    getMeasuredHeight());
+        }
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java b/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
index eb36855..3dfbbc1 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintResources.java
@@ -25,12 +25,12 @@
  *
  * @hide
  */
-class TintResources extends Resources {
+class TintResources extends ResourcesWrapper {
 
     private final TintManager mTintManager;
 
     public TintResources(Resources resources, TintManager tintManager) {
-        super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+        super(resources);
         mTintManager = tintManager;
     }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java b/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
index e7e79cd..a1b99bc 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
@@ -16,24 +16,35 @@
 
 package android.support.v7.internal.widget;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v4.view.TintableBackgroundView;
 import android.util.AttributeSet;
-import android.widget.EditText;
+import android.widget.ListPopupWindow;
 import android.widget.Spinner;
 
+import java.lang.reflect.Field;
+
 /**
  * An tint aware {@link android.widget.Spinner}.
- *
- * @hide
+ * <p>
+ * This will automatically be used when you use {@link android.widget.Spinner} in your
+ * layouts. You should only need to manually use this class when writing custom views.
  */
-public class TintSpinner extends Spinner {
+public class TintSpinner extends Spinner implements TintableBackgroundView {
 
     private static final int[] TINT_ATTRS = {
             android.R.attr.background,
             android.R.attr.popupBackground
     };
 
+    private TintInfo mBackgroundTint;
+
     public TintSpinner(Context context) {
         this(context, null);
     }
@@ -45,15 +56,108 @@
     public TintSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
-                defStyleAttr, 0);
-        setBackgroundDrawable(a.getDrawable(0));
-
-        if (Build.VERSION.SDK_INT >= 16 && a.hasValue(1)) {
-            setPopupBackgroundDrawable(a.getDrawable(1));
+        if (TintManager.SHOULD_BE_USED) {
+            TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
+                    TINT_ATTRS, defStyleAttr, 0);
+            if (a.hasValue(0)) {
+                setSupportBackgroundTintList(
+                        a.getTintManager().getColorStateList(a.getResourceId(0, -1)));
+            }
+            if (a.hasValue(1)) {
+                final Drawable popupBackground = a.getDrawable(1);
+                if (Build.VERSION.SDK_INT >= 16) {
+                    setPopupBackgroundDrawable(popupBackground);
+                } else if (Build.VERSION.SDK_INT >= 11) {
+                    setPopupBackgroundDrawableV11(this, popupBackground);
+                }
+            }
+            a.recycle();
         }
+    }
 
-        a.recycle();
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    private static void setPopupBackgroundDrawableV11(Spinner view, Drawable background) {
+        try {
+            Field popupField = Spinner.class.getDeclaredField("mPopup");
+            popupField.setAccessible(true);
+
+            Object popup = popupField.get(view);
+
+            if (popup instanceof ListPopupWindow) {
+                ((ListPopupWindow) popup).setBackgroundDrawable(background);
+            }
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(android.view.View,
+     * android.content.res.ColorStateList)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintList(@Nullable ColorStateList tint) {
+        if (mBackgroundTint == null && tint != null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintList = tint;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintList(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public ColorStateList getSupportBackgroundTintList() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintList : null;
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode)}
+     *
+     * @hide
+     */
+    @Override
+    public void setSupportBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
+        if (mBackgroundTint == null) {
+            mBackgroundTint = new TintInfo();
+        }
+        mBackgroundTint.mTintMode = tintMode;
+        applySupportBackgroundTint();
+    }
+
+    /**
+     * This should be accessed via
+     * {@link android.support.v4.view.ViewCompat#getBackgroundTintMode(android.view.View)}
+     *
+     * @hide
+     */
+    @Override
+    @Nullable
+    public PorterDuff.Mode getSupportBackgroundTintMode() {
+        return mBackgroundTint != null ? mBackgroundTint.mTintMode : null;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        applySupportBackgroundTint();
+    }
+
+    private void applySupportBackgroundTint() {
+        if (getBackground() != null && mBackgroundTint != null) {
+            TintManager.tintViewBackground(this, mBackgroundTint);
+        }
     }
 
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java b/v7/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java
index aff509c..221e2a1 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintTypedArray.java
@@ -179,7 +179,9 @@
 
     public TintManager getTintManager() {
         if (mTintManager == null) {
-            mTintManager = new TintManager(mContext);
+            mTintManager = (mContext instanceof TintContextWrapper)
+                    ? ((TintContextWrapper) mContext).getTintManager()
+                    : new TintManager(mContext);
         }
         return mTintManager;
     }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
index 9d8fc7c..b466010 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
@@ -24,7 +24,6 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
 import android.support.v7.appcompat.R;
-import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.view.menu.ActionMenuItem;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPresenter;
@@ -74,7 +73,7 @@
     private CharSequence mSubtitle;
     private CharSequence mHomeDescription;
 
-    private WindowCallback mWindowCallback;
+    private Window.Callback mWindowCallback;
     private boolean mMenuPrepared;
     private ActionMenuPresenter mActionMenuPresenter;
 
@@ -95,6 +94,7 @@
         mTitle = toolbar.getTitle();
         mSubtitle = toolbar.getSubtitle();
         mTitleSet = mTitle != null;
+        mNavIcon = toolbar.getNavigationIcon();
 
         if (style) {
             final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
@@ -116,7 +116,7 @@
             }
 
             final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
-            if (icon != null) {
+            if (mNavIcon == null && icon != null) {
                 setIcon(icon);
             }
 
@@ -256,7 +256,7 @@
     }
 
     @Override
-    public void setWindowCallback(WindowCallback cb) {
+    public void setWindowCallback(Window.Callback cb) {
         mWindowCallback = cb;
     }
 
@@ -676,4 +676,41 @@
         mToolbar.restoreHierarchyState(toolbarStates);
     }
 
+    @Override
+    public void setBackgroundDrawable(Drawable d) {
+        //noinspection deprecation
+        mToolbar.setBackgroundDrawable(d);
+    }
+
+    @Override
+    public int getHeight() {
+        return mToolbar.getHeight();
+    }
+
+    @Override
+    public void setVisibility(int visible) {
+        mToolbar.setVisibility(visible);
+    }
+
+    @Override
+    public int getVisibility() {
+        return mToolbar.getVisibility();
+    }
+
+    @Override
+    public void setMenuCallbacks(MenuPresenter.Callback actionMenuPresenterCallback,
+            MenuBuilder.Callback menuBuilderCallback) {
+        mToolbar.setMenuCallbacks(actionMenuPresenterCallback, menuBuilderCallback);
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mToolbar.getMenu();
+    }
+
+    @Override
+    public int getPopupTheme() {
+        return mToolbar.getPopupTheme();
+    }
+
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java b/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java
index a56cf3f..98e55d5 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java
@@ -16,9 +16,14 @@
 
 package android.support.v7.internal.widget;
 
+import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.os.Build;
 import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.ContextThemeWrapper;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 
@@ -102,4 +107,34 @@
             }
         }
     }
+
+    /**
+     * Allows us to emulate the {@code android:theme} attribute for devices before L.
+     */
+    public static Context themifyContext(Context context, AttributeSet attrs,
+            boolean useAndroidTheme, boolean useAppTheme) {
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
+        int themeId = 0;
+        if (useAndroidTheme) {
+            // First try reading android:theme if enabled
+            themeId = a.getResourceId(R.styleable.View_android_theme, 0);
+        }
+        if (useAppTheme && themeId == 0) {
+            // ...if that didn't work, try reading app:theme (for legacy reasons) if enabled
+            themeId = a.getResourceId(R.styleable.View_theme, 0);
+
+            if (themeId != 0) {
+                Log.i(TAG, "app:theme is now deprecated. Please move to using android:theme instead.");
+            }
+        }
+        a.recycle();
+
+        if (themeId != 0 && (!(context instanceof ContextThemeWrapper)
+                || ((ContextThemeWrapper) context).getThemeResId() != themeId)) {
+            // If the context isn't a ContextThemeWrapperCompat, or it is but does not have
+            // the same theme as we need, wrap it in a new wrapper
+            context = new ContextThemeWrapper(context, themeId);
+        }
+        return context;
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/view/ActionMode.java b/v7/appcompat/src/android/support/v7/view/ActionMode.java
index 579305f..ff9cd49 100644
--- a/v7/appcompat/src/android/support/v7/view/ActionMode.java
+++ b/v7/appcompat/src/android/support/v7/view/ActionMode.java
@@ -222,7 +222,7 @@
 
     /**
      * Callback interface for action modes. Supplied to
-     * {@link android.support.v7.app.ActionBarActivity#startSupportActionMode(Callback)} (Callback)},
+     * {@link android.support.v7.app.AppCompatDelegate#startSupportActionMode(Callback)} (Callback)},
      * a Callback configures and handles events raised by a user's interaction with an action mode.
      *
      * <p>An action mode's lifecycle is as follows:
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
index 801be5b..bf167d64 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
@@ -652,11 +652,15 @@
             final Drawable d = getDrawable();
             final Drawable bg = getBackground();
             if (d != null && bg != null) {
-                final float[] pts = mTempPts;
-                pts[0] = d.getBounds().centerX();
-                getImageMatrix().mapPoints(pts);
-                final int offset =  (int) pts[0] - getWidth() / 2;
-                DrawableCompat.setHotspotBounds(bg, offset, 0, getWidth() + offset, getHeight());
+                final int width = getWidth();
+                final int height = getHeight();
+                final int halfEdge = Math.max(width, height) / 2;
+                final int offsetX = getPaddingLeft() - getPaddingRight();
+                final int offsetY = getPaddingTop() - getPaddingBottom();
+                final int centerX = (width + offsetX) / 2;
+                final int centerY = (height + offsetY) / 2;
+                DrawableCompat.setHotspotBounds(bg, centerX - halfEdge, centerY - halfEdge,
+                        centerX + halfEdge, centerY + halfEdge);
             }
 
             return changed;
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
index d1f83d4..b7c9821 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
@@ -126,11 +126,13 @@
             super.onConfigurationChanged(newConfig);
         }
 
-        mPresenter.updateMenuView(false);
+        if (mPresenter != null) {
+            mPresenter.updateMenuView(false);
 
-        if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
-            mPresenter.hideOverflowMenu();
-            mPresenter.showOverflowMenu();
+            if (mPresenter.isOverflowMenuShowing()) {
+                mPresenter.hideOverflowMenu();
+                mPresenter.showOverflowMenu();
+            }
         }
     }
 
@@ -439,7 +441,7 @@
         }
 
         final int childCount = getChildCount();
-        final int midVertical = (top + bottom) / 2;
+        final int midVertical = (bottom - top) / 2;
         final int dividerWidth = getDividerWidth();
         int overflowWidth = 0;
         int nonOverflowWidth = 0;
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index 687b9c8..0d2bcb8 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -212,10 +212,23 @@
      * @param defStyleAttr Default style attribute to use for popup content.
      */
     public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     *
+     * @param context Context used for contained views.
+     * @param attrs Attributes from inflating parent views used to style the popup.
+     * @param defStyleAttr Style attribute to read for default styling of popup content.
+     * @param defStyleRes Style resource ID to use for default styling of popup content.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         mContext = context;
 
         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,
-                defStyleAttr, 0);
+                defStyleAttr, defStyleRes);
         mDropDownHorizontalOffset = a.getDimensionPixelOffset(
                 R.styleable.ListPopupWindow_android_dropDownHorizontalOffset, 0);
         mDropDownVerticalOffset = a.getDimensionPixelOffset(
@@ -1374,7 +1387,9 @@
             clearCallbacks();
 
             final View src = mSrc;
-            if (!src.isEnabled()) {
+            if (!src.isEnabled() || src.isLongClickable()) {
+                // Ignore long-press if the view is disabled or has its own
+                // handler.
                 return;
             }
 
@@ -1383,12 +1398,12 @@
             }
 
             // Don't let the parent intercept our events.
-            mSrc.getParent().requestDisallowInterceptTouchEvent(true);
+            src.getParent().requestDisallowInterceptTouchEvent(true);
 
             // Make sure we cancel any ongoing source event stream.
             final long now = SystemClock.uptimeMillis();
             final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
-            mSrc.onTouchEvent(e);
+            src.onTouchEvent(e);
             e.recycle();
 
             mForwarding = true;
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 14813b1..5b7d333b 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.support.annotation.MenuRes;
+import android.support.v7.appcompat.R;
 import android.support.v7.internal.view.SupportMenuInflater;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPopupHelper;
@@ -70,20 +71,44 @@
     }
 
     /**
-     * Construct a new PopupMenu.
+     * Constructor to create a new popup menu with an anchor view and alignment
+     * gravity.
      *
-     * @param context Context for the PopupMenu.
-     * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
-     *               is room, or above it if there is not.
-     * @param gravity The {@link Gravity} value for aligning the popup with its anchor
+     * @param context Context the popup menu is running in, through which it
+     *        can access the current theme, resources, etc.
+     * @param anchor Anchor view for this popup. The popup will appear below
+     *        the anchor if there is room, or above it if there is not.
+     * @param gravity The {@link Gravity} value for aligning the popup with its
+     *        anchor.
      */
     public PopupMenu(Context context, View anchor, int gravity) {
-        // TODO Theme?
+        this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
+    }
+
+    /**
+     * Constructor a create a new popup menu with a specific style.
+     *
+     * @param context Context the popup menu is running in, through which it
+     *        can access the current theme, resources, etc.
+     * @param anchor Anchor view for this popup. The popup will appear below
+     *        the anchor if there is room, or above it if there is not.
+     * @param gravity The {@link Gravity} value for aligning the popup with its
+     *        anchor.
+     * @param popupStyleAttr An attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the popup window. Can be 0 to not look for defaults.
+     * @param popupStyleRes A resource identifier of a style resource that
+     *        supplies default values for the popup window, used only if
+     *        popupStyleAttr is 0 or can not be found in the theme. Can be 0
+     *        to not look for defaults.
+     */
+    public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
+            int popupStyleRes) {
         mContext = context;
         mMenu = new MenuBuilder(context);
         mMenu.setCallback(this);
         mAnchor = anchor;
-        mPopup = new MenuPopupHelper(context, mMenu, anchor);
+        mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
         mPopup.setCallback(this);
     }
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index fbf3b24..2eb8778 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -39,6 +39,7 @@
 import android.support.v4.view.KeyEventCompat;
 import android.support.v4.widget.CursorAdapter;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.TintAutoCompleteTextView;
 import android.support.v7.internal.widget.TintManager;
 import android.support.v7.internal.widget.TintTypedArray;
 import android.support.v7.internal.widget.ViewUtils;
@@ -113,17 +114,21 @@
      */
     private static final String IME_OPTION_NO_MICROPHONE = "nm";
 
-    private final SearchAutoComplete mQueryTextView;
+    private final SearchAutoComplete mSearchSrcTextView;
     private final View mSearchEditFrame;
     private final View mSearchPlate;
     private final View mSubmitArea;
     private final ImageView mSearchButton;
-    private final ImageView mSubmitButton;
+    private final ImageView mGoButton;
     private final ImageView mCloseButton;
     private final ImageView mVoiceButton;
-    private final ImageView mSearchHintIcon;
     private final View mDropDownAnchor;
-    private final int mSearchIconResId;
+
+    /** Icon optionally displayed when the SearchView is collapsed. */
+    private final ImageView mCollapsedIcon;
+
+    /** Drawable used as an EditText hint. */
+    private final Drawable mSearchHintIcon;
 
     // Resources used by SuggestionsAdapter to display suggestions.
     private final int mSuggestionRowLayout;
@@ -132,6 +137,7 @@
     // Intents used for voice searching.
     private final Intent mVoiceWebSearchIntent;
     private final Intent mVoiceAppSearchIntent;
+
     private OnQueryTextListener mOnQueryChangeListener;
     private OnCloseListener mOnCloseListener;
     private OnFocusChangeListener mOnQueryTextFocusChangeListener;
@@ -276,48 +282,53 @@
         // Keep the TintManager in case we need it later
         mTintManager = a.getTintManager();
 
-        final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        final int layoutResId = a.getResourceId(R.styleable.SearchView_layout, 0);
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final int layoutResId = a.getResourceId(
+                R.styleable.SearchView_layout, R.layout.abc_search_view);
         inflater.inflate(layoutResId, this, true);
-        mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
-        mQueryTextView.setSearchView(this);
+
+        mSearchSrcTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
+        mSearchSrcTextView.setSearchView(this);
 
         mSearchEditFrame = findViewById(R.id.search_edit_frame);
         mSearchPlate = findViewById(R.id.search_plate);
         mSubmitArea = findViewById(R.id.submit_area);
         mSearchButton = (ImageView) findViewById(R.id.search_button);
-        mSubmitButton = (ImageView) findViewById(R.id.search_go_btn);
+        mGoButton = (ImageView) findViewById(R.id.search_go_btn);
         mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
         mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn);
-        mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);
+        mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon);
+
         // Set up icons and backgrounds.
         mSearchPlate.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_queryBackground));
         mSubmitArea.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_submitBackground));
-        mSearchIconResId = a.getResourceId(R.styleable.SearchView_searchIcon, 0);
-        mSearchButton.setImageResource(mSearchIconResId);
-        mSubmitButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
+        mSearchButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+        mGoButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
         mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon));
         mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon));
-        mSearchHintIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+        mCollapsedIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+
+        mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchHintIcon);
 
         // Extract dropdown layout resource IDs for later use.
-        mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout, 0);
+        mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout,
+                R.layout.abc_search_dropdown_item_icons_2line);
         mSuggestionCommitIconResId = a.getResourceId(R.styleable.SearchView_commitIcon, 0);
 
         mSearchButton.setOnClickListener(mOnClickListener);
         mCloseButton.setOnClickListener(mOnClickListener);
-        mSubmitButton.setOnClickListener(mOnClickListener);
+        mGoButton.setOnClickListener(mOnClickListener);
         mVoiceButton.setOnClickListener(mOnClickListener);
-        mQueryTextView.setOnClickListener(mOnClickListener);
+        mSearchSrcTextView.setOnClickListener(mOnClickListener);
 
-        mQueryTextView.addTextChangedListener(mTextWatcher);
-        mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
-        mQueryTextView.setOnItemClickListener(mOnItemClickListener);
-        mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
-        mQueryTextView.setOnKeyListener(mTextKeyListener);
+        mSearchSrcTextView.addTextChangedListener(mTextWatcher);
+        mSearchSrcTextView.setOnEditorActionListener(mOnEditorActionListener);
+        mSearchSrcTextView.setOnItemClickListener(mOnItemClickListener);
+        mSearchSrcTextView.setOnItemSelectedListener(mOnItemSelectedListener);
+        mSearchSrcTextView.setOnKeyListener(mTextKeyListener);
+
         // Inform any listener of focus changes
-        mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
+        mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
 
             public void onFocusChange(View v, boolean hasFocus) {
                 if (mOnQueryTextFocusChangeListener != null) {
@@ -331,14 +342,17 @@
         if (maxWidth != -1) {
             setMaxWidth(maxWidth);
         }
+
         final CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint);
         if (!TextUtils.isEmpty(queryHint)) {
             setQueryHint(queryHint);
         }
+
         final int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1);
         if (imeOptions != -1) {
             setImeOptions(imeOptions);
         }
+
         final int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1);
         if (inputType != -1) {
             setInputType(inputType);
@@ -359,7 +373,7 @@
         mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
         mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
-        mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
+        mDropDownAnchor = findViewById(mSearchSrcTextView.getDropDownAnchor());
         if (mDropDownAnchor != null) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                 addOnLayoutChangeListenerToDropDownAnchorSDK11();
@@ -423,7 +437,7 @@
         if (mVoiceButtonEnabled) {
             // Disable the microphone on the keyboard, as a mic is displayed near the text box
             // TODO: use imeOptions to disable voice input when the new API will be available
-            mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
+            mSearchSrcTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
         }
         updateViewsVisibility(isIconified());
     }
@@ -444,7 +458,7 @@
      * @param imeOptions the options to set on the query text field
      */
     public void setImeOptions(int imeOptions) {
-        mQueryTextView.setImeOptions(imeOptions);
+        mSearchSrcTextView.setImeOptions(imeOptions);
     }
 
     /**
@@ -453,7 +467,7 @@
      * @see TextView#setImeOptions(int)
      */
     public int getImeOptions() {
-        return mQueryTextView.getImeOptions();
+        return mSearchSrcTextView.getImeOptions();
     }
 
     /**
@@ -463,7 +477,7 @@
      * @param inputType the input type to set on the query text field
      */
     public void setInputType(int inputType) {
-        mQueryTextView.setInputType(inputType);
+        mSearchSrcTextView.setInputType(inputType);
     }
 
     /**
@@ -471,7 +485,7 @@
      * @return the input type
      */
     public int getInputType() {
-        return mQueryTextView.getInputType();
+        return mSearchSrcTextView.getInputType();
     }
 
     /** @hide */
@@ -483,7 +497,7 @@
         if (!isFocusable()) return false;
         // If it is not iconified, then give the focus to the text field
         if (!isIconified()) {
-            boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
+            boolean result = mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect);
             if (result) {
                 updateViewsVisibility(false);
             }
@@ -499,7 +513,7 @@
         mClearingFocus = true;
         setImeVisibility(false);
         super.clearFocus();
-        mQueryTextView.clearFocus();
+        mSearchSrcTextView.clearFocus();
         mClearingFocus = false;
     }
 
@@ -558,7 +572,7 @@
      * @return the query string
      */
     public CharSequence getQuery() {
-        return mQueryTextView.getText();
+        return mSearchSrcTextView.getText();
     }
 
     /**
@@ -570,9 +584,9 @@
      * text field.
      */
     public void setQuery(CharSequence query, boolean submit) {
-        mQueryTextView.setText(query);
+        mSearchSrcTextView.setText(query);
         if (query != null) {
-            mQueryTextView.setSelection(mQueryTextView.length());
+            mSearchSrcTextView.setSelection(mSearchSrcTextView.length());
             mUserQuery = query;
         }
 
@@ -725,7 +739,7 @@
     public void setSuggestionsAdapter(CursorAdapter adapter) {
         mSuggestionsAdapter = adapter;
 
-        mQueryTextView.setAdapter(mSuggestionsAdapter);
+        mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
     }
 
     /**
@@ -799,12 +813,12 @@
         // Visibility of views that are visible when collapsed
         final int visCollapsed = collapsed ? VISIBLE : GONE;
         // Is there text in the query
-        final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+        final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
 
         mSearchButton.setVisibility(visCollapsed);
         updateSubmitButton(hasText);
         mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
-        mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
+        mCollapsedIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
         updateCloseButton();
         updateVoiceButton(!hasText);
         updateSubmitArea();
@@ -812,8 +826,7 @@
 
     @TargetApi(Build.VERSION_CODES.FROYO)
     private boolean hasVoiceSearch() {
-        if (mSearchable != null &&
-                mSearchable.getVoiceSearchEnabled()) {
+        if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
             Intent testIntent = null;
             if (mSearchable.getVoiceSearchLaunchWebSearch()) {
                 testIntent = mVoiceWebSearchIntent;
@@ -839,26 +852,29 @@
                 && (hasText || !mVoiceButtonEnabled)) {
             visibility = VISIBLE;
         }
-        mSubmitButton.setVisibility(visibility);
+        mGoButton.setVisibility(visibility);
     }
 
     private void updateSubmitArea() {
         int visibility = GONE;
         if (isSubmitAreaEnabled()
-                && (mSubmitButton.getVisibility() == VISIBLE
-                || mVoiceButton.getVisibility() == VISIBLE)) {
+                && (mGoButton.getVisibility() == VISIBLE
+                        || mVoiceButton.getVisibility() == VISIBLE)) {
             visibility = VISIBLE;
         }
         mSubmitArea.setVisibility(visibility);
     }
 
     private void updateCloseButton() {
-        final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
+        final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
         // Should we show the close button? It is not shown if there's no focus,
         // field is not iconified by default and there is no text in it.
         final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
         mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
-        mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
+        final Drawable closeButtonImg = mCloseButton.getDrawable();
+        if (closeButtonImg != null){
+            closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
+        }
     }
 
     private void postUpdateFocusedState() {
@@ -866,9 +882,16 @@
     }
 
     private void updateFocusedState() {
-        boolean focused = mQueryTextView.hasFocus();
-        mSearchPlate.getBackground().setState(focused ? ENABLED_FOCUSED_STATE_SET : EMPTY_STATE_SET);
-        mSubmitArea.getBackground().setState(focused ? ENABLED_FOCUSED_STATE_SET : EMPTY_STATE_SET);
+        final boolean focused = mSearchSrcTextView.hasFocus();
+        final int[] stateSet = focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET;
+        final Drawable searchPlateBg = mSearchPlate.getBackground();
+        if (searchPlateBg != null) {
+            searchPlateBg.setState(stateSet);
+        }
+        final Drawable submitAreaBg = mSubmitArea.getBackground();
+        if (submitAreaBg != null) {
+            submitAreaBg.setState(stateSet);
+        }
         invalidate();
     }
 
@@ -908,13 +931,11 @@
                 onSearchClicked();
             } else if (v == mCloseButton) {
                 onCloseClicked();
-            } else if (v == mSubmitButton) {
+            } else if (v == mGoButton) {
                 onSubmitQuery();
             } else if (v == mVoiceButton) {
-                if (IS_AT_LEAST_FROYO) {
-                    onVoiceClicked();
-                }
-            } else if (v == mQueryTextView) {
+                onVoiceClicked();
+            } else if (v == mSearchSrcTextView) {
                 forceSuggestionQuery();
             }
         }
@@ -934,25 +955,25 @@
 
             if (DBG) {
                 Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
-                        + mQueryTextView.getListSelection());
+                        + mSearchSrcTextView.getListSelection());
             }
 
             // If a suggestion is selected, handle enter, search key, and action keys
             // as presses on the selected suggestion
-            if (mQueryTextView.isPopupShowing()
-                    && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
+            if (mSearchSrcTextView.isPopupShowing()
+                    && mSearchSrcTextView.getListSelection() != ListView.INVALID_POSITION) {
                 return onSuggestionsKey(v, keyCode, event);
             }
 
             // If there is text in the query box, handle enter, and action keys
             // The search key is handled by the dialog's onKeyDown().
-            if (!mQueryTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) {
+            if (!mSearchSrcTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) {
                 if (event.getAction() == KeyEvent.ACTION_UP) {
                     if (keyCode == KeyEvent.KEYCODE_ENTER) {
                         v.cancelLongPress();
 
                         // Launch as a regular search.
-                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
+                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mSearchSrcTextView.getText()
                                 .toString());
                         return true;
                     }
@@ -980,7 +1001,7 @@
             // "click")
             if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
                     || keyCode == KeyEvent.KEYCODE_TAB) {
-                int position = mQueryTextView.getListSelection();
+                int position = mSearchSrcTextView.getListSelection();
                 return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
             }
 
@@ -991,18 +1012,18 @@
                 // left key, at end if right key
                 // TODO: Reverse left/right for right-to-left languages, e.g.
                 // Arabic
-                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
+                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mSearchSrcTextView
                         .length();
-                mQueryTextView.setSelection(selPoint);
-                mQueryTextView.setListSelection(0);
-                mQueryTextView.clearListSelection();
-                HIDDEN_METHOD_INVOKER.ensureImeVisible(mQueryTextView, true);
+                mSearchSrcTextView.setSelection(selPoint);
+                mSearchSrcTextView.setListSelection(0);
+                mSearchSrcTextView.clearListSelection();
+                HIDDEN_METHOD_INVOKER.ensureImeVisible(mSearchSrcTextView, true);
 
                 return true;
             }
 
             // Next, check for an "up and out" move
-            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
+            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchSrcTextView.getListSelection()) {
                 // TODO: restoreUserQuery();
                 // let ACTV complete the move
                 return false;
@@ -1012,24 +1033,24 @@
     }
 
     private CharSequence getDecoratedHint(CharSequence hintText) {
-        // If the field is always expanded, then don't add the search icon to the hint
-        if (!mIconifiedByDefault) {
+        // If the field is always expanded or we don't have a search hint icon,
+        // then don't add the search icon to the hint.
+        if (!mIconifiedByDefault || mSearchHintIcon == null) {
             return hintText;
         }
 
-        final Drawable searchIcon = mTintManager.getDrawable(mSearchIconResId);
-        final int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
-        searchIcon.setBounds(0, 0, textSize, textSize);
+        final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25);
+        mSearchHintIcon.setBounds(0, 0, textSize, textSize);
 
-        final SpannableStringBuilder ssb = new SpannableStringBuilder("   "); // for the icon
+        final SpannableStringBuilder ssb = new SpannableStringBuilder("   ");
+        ssb.setSpan(new ImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         ssb.append(hintText);
-        ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         return ssb;
     }
 
     private void updateQueryHint() {
         if (mQueryHint != null) {
-            mQueryTextView.setHint(getDecoratedHint(mQueryHint));
+            mSearchSrcTextView.setHint(getDecoratedHint(mQueryHint));
         } else if (IS_AT_LEAST_FROYO && mSearchable != null) {
             CharSequence hint = null;
             int hintId = mSearchable.getHintId();
@@ -1037,10 +1058,10 @@
                 hint = getContext().getString(hintId);
             }
             if (hint != null) {
-                mQueryTextView.setHint(getDecoratedHint(hint));
+                mSearchSrcTextView.setHint(getDecoratedHint(hint));
             }
         } else {
-            mQueryTextView.setHint(getDecoratedHint(""));
+            mSearchSrcTextView.setHint(getDecoratedHint(""));
         }
     }
 
@@ -1049,8 +1070,8 @@
      */
     @TargetApi(Build.VERSION_CODES.FROYO)
     private void updateSearchAutoComplete() {
-        mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
-        mQueryTextView.setImeOptions(mSearchable.getImeOptions());
+        mSearchSrcTextView.setThreshold(mSearchable.getSuggestThreshold());
+        mSearchSrcTextView.setImeOptions(mSearchable.getImeOptions());
         int inputType = mSearchable.getInputType();
         // We only touch this if the input type is set up for text (which it almost certainly
         // should be, in the case of search!)
@@ -1069,7 +1090,7 @@
                 inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
             }
         }
-        mQueryTextView.setInputType(inputType);
+        mSearchSrcTextView.setInputType(inputType);
         if (mSuggestionsAdapter != null) {
             mSuggestionsAdapter.changeCursor(null);
         }
@@ -1078,10 +1099,10 @@
         if (mSearchable.getSuggestAuthority() != null) {
             mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
                     this, mSearchable, mOutsideDrawablesCache);
-            mQueryTextView.setAdapter(mSuggestionsAdapter);
+            mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
             ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
                     mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
-                            : SuggestionsAdapter.REFINE_BY_ENTRY);
+                    : SuggestionsAdapter.REFINE_BY_ENTRY);
         }
     }
 
@@ -1095,7 +1116,7 @@
         int visibility = GONE;
         if (mVoiceButtonEnabled && !isIconified() && empty) {
             visibility = VISIBLE;
-            mSubmitButton.setVisibility(GONE);
+            mGoButton.setVisibility(GONE);
         }
         mVoiceButton.setVisibility(visibility);
     }
@@ -1112,7 +1133,7 @@
     };
 
     private void onTextChanged(CharSequence newText) {
-        CharSequence text = mQueryTextView.getText();
+        CharSequence text = mSearchSrcTextView.getText();
         mUserQuery = text;
         boolean hasText = !TextUtils.isEmpty(text);
         updateSubmitButton(hasText);
@@ -1126,7 +1147,7 @@
     }
 
     private void onSubmitQuery() {
-        CharSequence query = mQueryTextView.getText();
+        CharSequence query = mSearchSrcTextView.getText();
         if (query != null && TextUtils.getTrimmedLength(query) > 0) {
             if (mOnQueryChangeListener == null
                     || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
@@ -1140,11 +1161,11 @@
     }
 
     private void dismissSuggestions() {
-        mQueryTextView.dismissDropDown();
+        mSearchSrcTextView.dismissDropDown();
     }
 
     private void onCloseClicked() {
-        CharSequence text = mQueryTextView.getText();
+        CharSequence text = mSearchSrcTextView.getText();
         if (TextUtils.isEmpty(text)) {
             if (mIconifiedByDefault) {
                 // If the app doesn't override the close behavior
@@ -1156,8 +1177,8 @@
                 }
             }
         } else {
-            mQueryTextView.setText("");
-            mQueryTextView.requestFocus();
+            mSearchSrcTextView.setText("");
+            mSearchSrcTextView.requestFocus();
             setImeVisibility(true);
         }
 
@@ -1165,7 +1186,7 @@
 
     private void onSearchClicked() {
         updateViewsVisibility(false);
-        mQueryTextView.requestFocus();
+        mSearchSrcTextView.requestFocus();
         setImeVisibility(true);
         if (mOnSearchClickListener != null) {
             mOnSearchClickListener.onClick(this);
@@ -1201,7 +1222,7 @@
         // Delayed update to make sure that the focus has settled down and window focus changes
         // don't affect it. A synchronous update was not working.
         postUpdateFocusedState();
-        if (mQueryTextView.hasFocus()) {
+        if (mSearchSrcTextView.hasFocus()) {
             forceSuggestionQuery();
         }
     }
@@ -1221,7 +1242,7 @@
         setQuery("", false);
         clearFocus();
         updateViewsVisibility(true);
-        mQueryTextView.setImeOptions(mCollapsedImeOptions);
+        mSearchSrcTextView.setImeOptions(mCollapsedImeOptions);
         mExpandedInActionView = false;
     }
 
@@ -1233,9 +1254,9 @@
         if (mExpandedInActionView) return;
 
         mExpandedInActionView = true;
-        mCollapsedImeOptions = mQueryTextView.getImeOptions();
-        mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
-        mQueryTextView.setText("");
+        mCollapsedImeOptions = mSearchSrcTextView.getImeOptions();
+        mSearchSrcTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+        mSearchSrcTextView.setText("");
         setIconified(false);
     }
 
@@ -1250,17 +1271,17 @@
                     ? res.getDimensionPixelSize(R.dimen.abc_dropdownitem_icon_width)
                     + res.getDimensionPixelSize(R.dimen.abc_dropdownitem_text_padding_left)
                     : 0;
-            mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
+            mSearchSrcTextView.getDropDownBackground().getPadding(dropDownPadding);
             int offset;
             if (isLayoutRtl) {
                 offset = - dropDownPadding.left;
             } else {
                 offset = anchorPadding - (dropDownPadding.left + iconOffset);
             }
-            mQueryTextView.setDropDownHorizontalOffset(offset);
+            mSearchSrcTextView.setDropDownHorizontalOffset(offset);
             final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
                     + dropDownPadding.right + iconOffset - anchorPadding;
-            mQueryTextView.setDropDownWidth(width);
+            mSearchSrcTextView.setDropDownWidth(width);
         }
     }
 
@@ -1318,7 +1339,7 @@
      * Query rewriting.
      */
     private void rewriteQueryFromSuggestion(int position) {
-        CharSequence oldQuery = mQueryTextView.getText();
+        CharSequence oldQuery = mSearchSrcTextView.getText();
         Cursor c = mSuggestionsAdapter.getCursor();
         if (c == null) {
             return;
@@ -1384,9 +1405,9 @@
      * Sets the text in the query box, without updating the suggestions.
      */
     private void setQuery(CharSequence query) {
-        mQueryTextView.setText(query);
+        mSearchSrcTextView.setText(query);
         // Move the cursor to the end
-        mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
+        mSearchSrcTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
     }
 
     private void launchQuerySearch(int actionKey, String actionMsg, String query) {
@@ -1570,14 +1591,14 @@
                 rowNum = -1;
             }
             Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
-                    " returned exception.", e);
+                            " returned exception.", e);
             return null;
         }
     }
 
     private void forceSuggestionQuery() {
-        HIDDEN_METHOD_INVOKER.doBeforeTextChanged(mQueryTextView);
-        HIDDEN_METHOD_INVOKER.doAfterTextChanged(mQueryTextView);
+        HIDDEN_METHOD_INVOKER.doBeforeTextChanged(mSearchSrcTextView);
+        HIDDEN_METHOD_INVOKER.doAfterTextChanged(mSearchSrcTextView);
     }
 
     static boolean isLandscapeMode(Context context) {
@@ -1605,17 +1626,11 @@
      * Local subclass for AutoCompleteTextView.
      * @hide
      */
-    public static class SearchAutoComplete extends AutoCompleteTextView {
-
-        private final int[] POPUP_WINDOW_ATTRS = {
-                android.R.attr.popupBackground
-        };
+    public static class SearchAutoComplete extends TintAutoCompleteTextView {
 
         private int mThreshold;
         private SearchView mSearchView;
 
-        private final TintManager mTintManager;
-
         public SearchAutoComplete(Context context) {
             this(context, null);
         }
@@ -1627,16 +1642,6 @@
         public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
             super(context, attrs, defStyle);
             mThreshold = getThreshold();
-
-            TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
-                    POPUP_WINDOW_ATTRS, defStyle, 0);
-            if (a.hasValue(0)) {
-                setDropDownBackgroundDrawable(a.getDrawable(0));
-            }
-            a.recycle();
-
-            // Keep the TintManager in case we need it later
-            mTintManager = a.getTintManager();
         }
 
         void setSearchView(SearchView searchView) {
@@ -1649,11 +1654,6 @@
             mThreshold = threshold;
         }
 
-        @Override
-        public void setDropDownBackgroundResource(int id) {
-            setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
-        }
-
         /**
          * Returns true if the text field is empty, or contains only whitespace.
          */
diff --git a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
index 5c161dc..173e5fc 100644
--- a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
+++ b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
@@ -21,7 +21,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
+import android.os.Build;
 import android.support.v4.view.ActionProvider;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.widget.ActivityChooserModel;
@@ -179,9 +179,11 @@
     @Override
     public View onCreateActionView() {
         // Create the view and set its data model.
-        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
         ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
-        activityChooserView.setActivityChooserModel(dataModel);
+        if (!activityChooserView.isInEditMode()) {
+            ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
+            activityChooserView.setActivityChooserModel(dataModel);
+        }
 
         // Lookup and set the expand action icon.
         TypedValue outTypedValue = new TypedValue();
@@ -299,6 +301,12 @@
      * @see Intent#ACTION_SEND_MULTIPLE
      */
     public void setShareIntent(Intent shareIntent) {
+        if (shareIntent != null) {
+            final String action = shareIntent.getAction();
+            if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+                updateIntent(shareIntent);
+            }
+        }
         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
                 mShareHistoryFileName);
         dataModel.setIntent(shareIntent);
@@ -315,7 +323,11 @@
             final int itemId = item.getItemId();
             Intent launchIntent = dataModel.chooseActivity(itemId);
             if (launchIntent != null) {
-                launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+                final String action = launchIntent.getAction();
+                if (Intent.ACTION_SEND.equals(action) ||
+                        Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+                    updateIntent(launchIntent);
+                }
                 mContext.startActivity(launchIntent);
             }
             return true;
@@ -350,4 +362,15 @@
             return false;
         }
     }
+
+    private void updateIntent(Intent intent) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            // If we're on Lollipop, we can open the intent as a document
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        } else {
+            // Else, we will use the old CLEAR_WHEN_TASK_RESET flag
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        }
+    }
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
index 244ec77..6251e0e 100644
--- a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
+++ b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.widget.ResourceCursorAdapter;
 import android.support.v7.appcompat.R;
 import android.text.Spannable;
@@ -64,11 +65,12 @@
     static final int REFINE_BY_ENTRY = 1;
     static final int REFINE_ALL = 2;
 
-    private SearchManager mSearchManager;
-    private SearchView mSearchView;
-    private SearchableInfo mSearchable;
-    private Context mProviderContext;
-    private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+    private final SearchManager mSearchManager;
+    private final SearchView mSearchView;
+    private final SearchableInfo mSearchable;
+    private final Context mProviderContext;
+    private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
+    private final int mCommitIconResId;
     private boolean mClosed = false;
     private int mQueryRefinement = REFINE_BY_ENTRY;
 
@@ -88,15 +90,15 @@
     // private final Runnable mStartSpinnerRunnable;
     // private final Runnable mStopSpinnerRunnable;
 
-    public SuggestionsAdapter(Context context, SearchView searchView,
-            SearchableInfo searchable,
+    public SuggestionsAdapter(Context context, SearchView searchView, SearchableInfo searchable,
             WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
-        super(context, R.layout.abc_search_dropdown_item_icons_2line,
-                null,   // no initial cursor
-                true);  // auto-requery
+        super(context, searchView.getSuggestionRowLayout(), null /* no initial cursor */,
+                true /* auto-requery */);
         mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
         mSearchView = searchView;
         mSearchable = searchable;
+        mCommitIconResId = searchView.getSuggestionCommitIconResId();
+
         // set up provider resources (gives us icons, etc.)
         mProviderContext = context;
 
@@ -109,7 +111,7 @@
      * copied to the query text field.
      * <p>
      *
-     * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE},
+     * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
      * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
      */
     public void setQueryRefinement(int refineWhat) {
@@ -193,9 +195,9 @@
         Bundle extras = cursor != null ? cursor.getExtras() : null;
         if (DBG) {
             Log.d(LOG_TAG, "updateSpinnerState - extra = "
-                    + (extras != null
-                    ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
-                    : null));
+                + (extras != null
+                        ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+                        : null));
         }
         // Check if the Cursor indicates that the query is not complete and show the spinner
         if (extras != null
@@ -239,8 +241,12 @@
      */
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        View v = super.newView(context, cursor, parent);
+        final View v = super.newView(context, cursor, parent);
         v.setTag(new ChildViewCache(v));
+
+        // Set up icon.
+        final ImageView iconRefine = (ImageView) v.findViewById(R.id.edit_query);
+        iconRefine.setImageResource(mCommitIconResId);
         return v;
     }
 
@@ -309,7 +315,7 @@
         }
         if (mQueryRefinement == REFINE_ALL
                 || (mQueryRefinement == REFINE_BY_ENTRY
-                && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
+                        && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) {
             views.mIconRefine.setVisibility(View.VISIBLE);
             views.mIconRefine.setTag(views.mText1.getText());
             views.mIconRefine.setOnClickListener(this);
@@ -489,7 +495,7 @@
                 return drawable;
             }
             // Not cached, find it by resource ID
-            drawable = mProviderContext.getResources().getDrawable(resourceId);
+            drawable = ContextCompat.getDrawable(mProviderContext, resourceId);
             // Stick it in the cache, using the URI as key
             storeInIconCache(drawableUri, drawable);
             return drawable;
diff --git a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
index de5cf90..9c53920 100644
--- a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.widget;
 
-import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -25,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -33,6 +33,7 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.text.AllCapsTransformationMethod;
+import android.support.v7.internal.widget.DrawableUtils;
 import android.support.v7.internal.widget.TintManager;
 import android.support.v7.internal.widget.TintTypedArray;
 import android.support.v7.internal.widget.ViewUtils;
@@ -44,6 +45,7 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
@@ -75,11 +77,9 @@
     private static final int TOUCH_MODE_DOWN = 1;
     private static final int TOUCH_MODE_DRAGGING = 2;
 
-    private static final int[] TEXT_APPEARANCE_ATTRS = {
-            android.R.attr.textColor,
-            android.R.attr.textSize,
-            R.attr.textAllCaps
-    };
+    // We force the accessibility events to have a class name of Switch, since screen readers
+    // already know how to handle their events
+    private static final String ACCESSIBILITY_EVENT_CLASS_NAME = "android.widget.Switch";
 
     // Enum for the "typeface" XML parameter.
     private static final int SANS = 1;
@@ -192,7 +192,13 @@
         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
                 attrs, R.styleable.SwitchCompat, defStyleAttr, 0);
         mThumbDrawable = a.getDrawable(R.styleable.SwitchCompat_android_thumb);
+        if (mThumbDrawable != null) {
+            mThumbDrawable.setCallback(this);
+        }
         mTrackDrawable = a.getDrawable(R.styleable.SwitchCompat_track);
+        if (mTrackDrawable != null) {
+            mTrackDrawable.setCallback(this);
+        }
         mTextOn = a.getText(R.styleable.SwitchCompat_android_textOn);
         mTextOff = a.getText(R.styleable.SwitchCompat_android_textOff);
         mShowText = a.getBoolean(R.styleable.SwitchCompat_showText, true);
@@ -228,12 +234,12 @@
      * from the specified TextAppearance resource.
      */
     public void setSwitchTextAppearance(Context context, int resid) {
-        TypedArray appearance = context.obtainStyledAttributes(resid, TEXT_APPEARANCE_ATTRS);
+        TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance);
 
         ColorStateList colors;
         int ts;
 
-        colors = appearance.getColorStateList(0);
+        colors = appearance.getColorStateList(R.styleable.TextAppearance_android_textColor);
         if (colors != null) {
             mTextColors = colors;
         } else {
@@ -241,7 +247,7 @@
             mTextColors = getTextColors();
         }
 
-        ts = appearance.getDimensionPixelSize(1, 0);
+        ts = appearance.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
         if (ts != 0) {
             if (ts != mTextPaint.getTextSize()) {
                 mTextPaint.setTextSize(ts);
@@ -249,7 +255,13 @@
             }
         }
 
-        boolean allCaps = appearance.getBoolean(2, false);
+        int typefaceIndex, styleIndex;
+        typefaceIndex = appearance.getInt(R.styleable.TextAppearance_android_typeface, -1);
+        styleIndex = appearance.getInt(R.styleable.TextAppearance_android_textStyle, -1);
+
+        setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
+
+        boolean allCaps = appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
         if (allCaps) {
             mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
         } else {
@@ -259,6 +271,25 @@
         appearance.recycle();
     }
 
+    private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
+        Typeface tf = null;
+        switch (typefaceIndex) {
+            case SANS:
+                tf = Typeface.SANS_SERIF;
+                break;
+
+            case SERIF:
+                tf = Typeface.SERIF;
+                break;
+
+            case MONOSPACE:
+                tf = Typeface.MONOSPACE;
+                break;
+        }
+
+        setSwitchTypeface(tf, styleIndex);
+    }
+
     /**
      * Sets the typeface and style in which the text should be displayed on the
      * switch, and turns on the fake bold and italic bits in the Paint if the
@@ -536,6 +567,11 @@
         // thumb's padding (when present).
         int paddingLeft = padding.left;
         int paddingRight = padding.right;
+        if (mThumbDrawable != null) {
+            final Rect inset = DrawableUtils.getOpticalBounds(mThumbDrawable);
+            paddingLeft = Math.max(paddingLeft, inset.left);
+            paddingRight = Math.max(paddingRight, inset.right);
+        }
 
         final int switchWidth = Math.max(mSwitchMinWidth,
                 2 * mThumbWidth + paddingLeft + paddingRight);
@@ -577,6 +613,10 @@
      * @return true if (x, y) is within the target area of the switch thumb
      */
     private boolean hitThumb(float x, float y) {
+        if (mThumbDrawable == null) {
+            return false;
+        }
+
         // Relies on mTempRect, MUST be called first!
         final int thumbOffset = getThumbOffset();
 
@@ -686,6 +726,7 @@
         // Commit the change if the event is up and not canceled and the switch
         // has not been disabled during the drag.
         final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+        final boolean oldState = isChecked();
         final boolean newState;
         if (commitChange) {
             mVelocityTracker.computeCurrentVelocity(1000);
@@ -696,10 +737,13 @@
                 newState = getTargetCheckedState();
             }
         } else {
-            newState = isChecked();
+            newState = oldState;
         }
 
-        setChecked(newState);
+        if (newState != oldState) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            setChecked(newState);
+        }
         cancelSuperTouch(ev);
     }
 
@@ -752,7 +796,7 @@
         // recursively with a different value, so load the REAL value...
         checked = isChecked();
 
-        if (getWindowToken() != null) {
+        if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
             animateThumbToCheckedState(checked);
         } else {
             // Immediately move the thumb to the new position.
@@ -775,8 +819,9 @@
                 trackPadding.setEmpty();
             }
 
-            opticalInsetLeft = 0;
-            opticalInsetRight = 0;
+            final Rect insets = DrawableUtils.getOpticalBounds(mThumbDrawable);
+            opticalInsetLeft = Math.max(0, insets.left - trackPadding.left);
+            opticalInsetRight = Math.max(0, insets.right - trackPadding.right);
         }
 
         final int switchRight;
@@ -826,6 +871,13 @@
 
         int thumbInitialLeft = switchLeft + getThumbOffset();
 
+        final Rect thumbInsets;
+        if (mThumbDrawable != null) {
+            thumbInsets = DrawableUtils.getOpticalBounds(mThumbDrawable);
+        } else {
+            thumbInsets = DrawableUtils.INSETS_NONE;
+        }
+
         // Layout the track.
         if (mTrackDrawable != null) {
             mTrackDrawable.getPadding(padding);
@@ -838,6 +890,20 @@
             int trackTop = switchTop;
             int trackRight = switchRight;
             int trackBottom = switchBottom;
+            if (thumbInsets != null && !thumbInsets.isEmpty()) {
+                if (thumbInsets.left > padding.left) {
+                    trackLeft += thumbInsets.left - padding.left;
+                }
+                if (thumbInsets.top > padding.top) {
+                    trackTop += thumbInsets.top - padding.top;
+                }
+                if (thumbInsets.right > padding.right) {
+                    trackRight -= thumbInsets.right - padding.right;
+                }
+                if (thumbInsets.bottom > padding.bottom) {
+                    trackBottom -= thumbInsets.bottom - padding.bottom;
+                }
+            }
             mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom);
         }
 
@@ -879,7 +945,19 @@
 
         final Drawable thumbDrawable = mThumbDrawable;
         if (trackDrawable != null) {
-            trackDrawable.draw(canvas);
+            if (mSplitTrack && thumbDrawable != null) {
+                final Rect insets = DrawableUtils.getOpticalBounds(thumbDrawable);
+                thumbDrawable.copyBounds(padding);
+                padding.left += insets.left;
+                padding.right -= insets.right;
+
+                final int saveCount = canvas.save();
+                canvas.clipRect(padding, Region.Op.DIFFERENCE);
+                trackDrawable.draw(canvas);
+                canvas.restoreToCount(saveCount);
+            } else {
+                trackDrawable.draw(canvas);
+            }
         }
 
         final int saveCount = canvas.save();
@@ -957,7 +1035,16 @@
         if (mTrackDrawable != null) {
             final Rect padding = mTempRect;
             mTrackDrawable.getPadding(padding);
-            return mSwitchWidth - mThumbWidth - padding.left - padding.right;
+
+            final Rect insets;
+            if (mThumbDrawable != null) {
+                insets = DrawableUtils.getOpticalBounds(mThumbDrawable);
+            } else {
+                insets = DrawableUtils.INSETS_NONE;
+            }
+
+            return mSwitchWidth - mThumbWidth - padding.left - padding.right
+                    - insets.left - insets.right;
         } else {
             return 0;
         }
@@ -991,7 +1078,9 @@
 
     @Override
     public void drawableHotspotChanged(float x, float y) {
-        super.drawableHotspotChanged(x, y);
+        if (Build.VERSION.SDK_INT >= 21) {
+            super.drawableHotspotChanged(x, y);
+        }
 
         if (mThumbDrawable != null) {
             DrawableCompat.setHotspot(mThumbDrawable, x, y);
@@ -1020,8 +1109,7 @@
                 mTrackDrawable.jumpToCurrentState();
             }
 
-            if (mPositionAnimator != null && mPositionAnimator.hasStarted() &&
-                    !mPositionAnimator.hasEnded()) {
+            if (mPositionAnimator != null && !mPositionAnimator.hasEnded()) {
                 clearAnimation();
                 mPositionAnimator = null;
             }
@@ -1032,14 +1120,14 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        event.setClassName(SwitchCompat.class.getName());
+        event.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME);
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         if (Build.VERSION.SDK_INT >= 14) {
             super.onInitializeAccessibilityNodeInfo(info);
-            info.setClassName(SwitchCompat.class.getName());
+            info.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME);
             CharSequence switchText = isChecked() ? mTextOn : mTextOff;
             if (!TextUtils.isEmpty(switchText)) {
                 CharSequence oldText = info.getText();
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index 2330b04..fd7b328 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -69,7 +69,7 @@
  * {@link android.app.Activity Activity's} opaque window decor controlled by the framework,
  * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy.
  * An application may choose to designate a Toolbar as the action bar for an Activity
- * using the {@link android.support.v7.app.ActionBarActivity#setSupportActionBar(Toolbar)
+ * using the {@link android.support.v7.app.AppCompatActivity#setSupportActionBar(Toolbar)
  * setSupportActionBar()} method.</p>
  *
  * <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar
@@ -114,6 +114,7 @@
     private ImageView mLogoView;
 
     private Drawable mCollapseIcon;
+    private CharSequence mCollapseDescription;
     private ImageButton mCollapseButtonView;
     View mExpandedActionView;
 
@@ -146,6 +147,7 @@
     private int mSubtitleTextColor;
 
     private boolean mEatingTouch;
+    private boolean mEatingHover;
 
     // Clear me after use.
     private final ArrayList<View> mTempViews = new ArrayList<View>();
@@ -191,7 +193,9 @@
     }
 
     public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(themifyContext(context, attrs, defStyleAttr), attrs, defStyleAttr);
+        // We manually themify the context here so that we don't break apps which only
+        // use app:theme when running on >= Lollipop
+        super(ViewUtils.themifyContext(context, attrs, false, true), attrs, defStyleAttr);
 
         // Need to use getContext() here so that we use the themed context
         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attrs,
@@ -200,7 +204,7 @@
         mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
         mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
         mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity);
-        mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+        mButtonGravity = Gravity.TOP;
         mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
                 a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
 
@@ -246,6 +250,7 @@
         }
 
         mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+        mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
 
         final CharSequence title = a.getText(R.styleable.Toolbar_title);
         if (!TextUtils.isEmpty(title)) {
@@ -1001,6 +1006,7 @@
             mCollapseButtonView = new ImageButton(getContext(), null,
                     R.attr.toolbarNavigationButtonStyle);
             mCollapseButtonView.setImageDrawable(mCollapseIcon);
+            mCollapseButtonView.setContentDescription(mCollapseDescription);
             final LayoutParams lp = generateDefaultLayoutParams();
             lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
             lp.mViewType = LayoutParams.EXPANDED;
@@ -1095,6 +1101,30 @@
         return true;
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        // Same deal as onTouchEvent() above. Eat all hover events, but still
+        // respect the touch event dispatch contract.
+
+        final int action = MotionEventCompat.getActionMasked(ev);
+        if (action == MotionEvent.ACTION_HOVER_ENTER) {
+            mEatingHover = false;
+        }
+
+        if (!mEatingHover) {
+            final boolean handled = super.onHoverEvent(ev);
+            if (action == MotionEvent.ACTION_HOVER_ENTER && !handled) {
+                mEatingHover = true;
+            }
+        }
+
+        if (action == MotionEvent.ACTION_HOVER_EXIT || action == MotionEvent.ACTION_CANCEL) {
+            mEatingHover = false;
+        }
+
+        return true;
+    }
+
     private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
             int parentHeightSpec, int heightUsed, int heightConstraint) {
         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
@@ -1780,7 +1810,7 @@
      *
      * <p>Toolbar.LayoutParams extends ActionBar.LayoutParams for compatibility with existing
      * ActionBar API. See
-     * {@link android.support.v7.app.ActionBarActivity#setSupportActionBar(Toolbar)
+     * {@link android.support.v7.app.AppCompatActivity#setSupportActionBar(Toolbar)
      * ActionBarActivity.setActionBar}
      * for more info on how to use a Toolbar as your Activity's ActionBar.</p>
      */
@@ -1995,17 +2025,4 @@
         }
     }
 
-    /**
-     * Allows us to emulate the {@code android:theme} attribute for devices before L.
-     */
-    private static Context themifyContext(Context context, AttributeSet attrs, int defStyleAttr) {
-        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
-                defStyleAttr, 0);
-        final int themeId = a.getResourceId(R.styleable.Toolbar_theme, 0);
-        if (themeId != 0) {
-            context = new ContextThemeWrapper(context, themeId);
-        }
-        a.recycle();
-        return context;
-    }
 }
diff --git a/v7/appcompat/src/android/support/v7/widget/WindowCallbackWrapper.java b/v7/appcompat/src/android/support/v7/widget/WindowCallbackWrapper.java
deleted file mode 100644
index 867a9ef..0000000
--- a/v7/appcompat/src/android/support/v7/widget/WindowCallbackWrapper.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.widget;
-
-import android.support.v7.internal.app.WindowCallback;
-import android.support.v7.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-/**
- * A simple decorator stub for WindowCallback that passes through any calls
- * to the wrapped instance as a base implementation. Call super.foo() to call into
- * the wrapped callback for any subclasses.
- *
- * @hide for internal use
- */
-public class WindowCallbackWrapper implements WindowCallback {
-    private WindowCallback mWrapped;
-
-    public WindowCallbackWrapper(WindowCallback wrapped) {
-        if (wrapped == null) {
-            throw new IllegalArgumentException("Window callback may not be null");
-        }
-        mWrapped = wrapped;
-    }
-
-    @Override
-    public boolean onMenuItemSelected(int featureId, MenuItem menuItem) {
-        return mWrapped.onMenuItemSelected(featureId, menuItem);
-    }
-
-    @Override
-    public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        return mWrapped.onCreatePanelMenu(featureId, menu);
-    }
-
-    @Override
-    public boolean onPreparePanel(int featureId, View menuView, Menu menu) {
-        return mWrapped.onPreparePanel(featureId, menuView, menu);
-    }
-
-    @Override
-    public void onPanelClosed(int featureId, Menu menu) {
-        mWrapped.onPanelClosed(featureId, menu);
-    }
-
-    @Override
-    public boolean onMenuOpened(int featureId, Menu menu) {
-        return mWrapped.onMenuOpened(featureId, menu);
-    }
-
-    @Override
-    public ActionMode startActionMode(ActionMode.Callback callback) {
-        return mWrapped.startActionMode(callback);
-    }
-
-    @Override
-    public View onCreatePanelView(int featureId) {
-        return mWrapped.onCreatePanelView(featureId);
-    }
-}
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index 5c8da8c..ff60510 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -55,7 +55,7 @@
 # A helper sub-library that makes direct use of L APIs
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v7-cardview-api21
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
     android-support-v7-cardview-jellybean-mr1
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index 02c77f8..833ccc8 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -101,4 +101,9 @@
     public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
         setMaxElevation(cardView, getMaxElevation(cardView));
     }
+
+    @Override
+    public void setBackgroundColor(CardViewDelegate cardView, int color) {
+        ((RoundRectDrawable) (cardView.getBackground())).setColor(color);
+    }
 }
\ No newline at end of file
diff --git a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
index 5067487..3477761 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -118,10 +118,15 @@
 
     @Override
     public int getOpacity() {
-        return PixelFormat.OPAQUE;
+        return PixelFormat.TRANSLUCENT;
     }
 
     public float getRadius() {
         return mRadius;
     }
+
+    public void setColor(int color) {
+        mPaint.setColor(color);
+        invalidateSelf();
+    }
 }
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
index ae78303..24b902c 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
@@ -46,4 +46,6 @@
     void onCompatPaddingChanged(CardViewDelegate cardView);
 
     void onPreventCornerOverlapChanged(CardViewDelegate cardView);
+
+    void setBackgroundColor(CardViewDelegate cardView, int color);
 }
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
index f9cb50a..8f7adfc 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -37,30 +37,36 @@
             public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
                     Paint paint) {
                 final float twoRadius = cornerRadius * 2;
-                final float innerWidth = bounds.width() - twoRadius;
-                final float innerHeight = bounds.height() - twoRadius;
-                sCornerRect.set(bounds.left, bounds.top,
-                        bounds.left + cornerRadius * 2, bounds.top + cornerRadius * 2);
-
-                canvas.drawArc(sCornerRect, 180, 90, true, paint);
-                sCornerRect.offset(innerWidth, 0);
-                canvas.drawArc(sCornerRect, 270, 90, true, paint);
-                sCornerRect.offset(0, innerHeight);
-                canvas.drawArc(sCornerRect, 0, 90, true, paint);
-                sCornerRect.offset(-innerWidth, 0);
-                canvas.drawArc(sCornerRect, 90, 90, true, paint);
-
-                //draw top and bottom pieces
-                canvas.drawRect(bounds.left + cornerRadius, bounds.top,
-                        bounds.right - cornerRadius, bounds.top + cornerRadius,
-                        paint);
-                canvas.drawRect(bounds.left + cornerRadius,
-                        bounds.bottom - cornerRadius, bounds.right - cornerRadius,
-                        bounds.bottom, paint);
-
-                //center
-                canvas.drawRect(bounds.left, bounds.top + cornerRadius,
-                        bounds.right, bounds.bottom - cornerRadius, paint);
+                final float innerWidth = bounds.width() - twoRadius - 1;
+                final float innerHeight = bounds.height() - twoRadius - 1;
+                // increment it to account for half pixels.
+                if (cornerRadius >= 1f) {
+                    cornerRadius += .5f;
+                    sCornerRect.set(-cornerRadius, -cornerRadius, cornerRadius, cornerRadius);
+                    int saved = canvas.save();
+                    canvas.translate(bounds.left + cornerRadius, bounds.top + cornerRadius);
+                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
+                    canvas.translate(innerWidth, 0);
+                    canvas.rotate(90);
+                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
+                    canvas.translate(innerHeight, 0);
+                    canvas.rotate(90);
+                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
+                    canvas.translate(innerWidth, 0);
+                    canvas.rotate(90);
+                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
+                    canvas.restoreToCount(saved);
+                    //draw top and bottom pieces
+                    canvas.drawRect(bounds.left + cornerRadius - 1f, bounds.top,
+                            bounds.right - cornerRadius + 1f, bounds.top + cornerRadius,
+                            paint);
+                    canvas.drawRect(bounds.left + cornerRadius - 1f,
+                            bounds.bottom - cornerRadius + 1f, bounds.right - cornerRadius + 1f,
+                            bounds.bottom, paint);
+                }
+////                center
+                canvas.drawRect(bounds.left, bounds.top + Math.max(0, cornerRadius - 1f),
+                        bounds.right, bounds.bottom - cornerRadius + 1f, paint);
             }
         };
     }
@@ -85,8 +91,8 @@
     public void updatePadding(CardViewDelegate cardView) {
         Rect shadowPadding = new Rect();
         getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
-        ((View)cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
-        ((View)cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
+        ((View) cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
+        ((View) cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
         cardView.setShadowPadding(shadowPadding.left, shadowPadding.top,
                 shadowPadding.right, shadowPadding.bottom);
     }
@@ -103,6 +109,11 @@
     }
 
     @Override
+    public void setBackgroundColor(CardViewDelegate cardView, int color) {
+        getShadowBackground(cardView).setColor(color);
+    }
+
+    @Override
     public void setRadius(CardViewDelegate cardView, float radius) {
         getShadowBackground(cardView).setCornerRadius(radius);
         updatePadding(cardView);
@@ -147,4 +158,4 @@
     private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
         return ((RoundRectDrawableWithShadow) cardView.getBackground());
     }
-}
\ No newline at end of file
+}
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
index 90bfef0..054ada8 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
@@ -28,7 +28,6 @@
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.support.v7.cardview.R;
-import android.util.Log;
 
 /**
  * A rounded rectangle drawable which also includes a shadow around.
@@ -39,7 +38,7 @@
 
     final static float SHADOW_MULTIPLIER = 1.5f;
 
-    final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
+    final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
 
     /*
     * This helper is set by CardView implementations.
@@ -90,16 +89,27 @@
             float shadowSize, float maxShadowSize) {
         mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
         mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
-        mInsetShadow = resources.getDimension(R.dimen.cardview_compat_inset_shadow);
-        setShadowSize(shadowSize, maxShadowSize);
+        mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
         mPaint.setColor(backgroundColor);
         mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
         mCornerShadowPaint.setStyle(Paint.Style.FILL);
-        mCornerShadowPaint.setDither(true);
-        mCornerRadius = radius;
+        mCornerRadius = (int) (radius + .5f);
         mCardBounds = new RectF();
         mEdgeShadowPaint = new Paint(mCornerShadowPaint);
+        mEdgeShadowPaint.setAntiAlias(false);
+        setShadowSize(shadowSize, maxShadowSize);
+    }
+
+    /**
+     * Casts the value to an even integer.
+     */
+    private int toEven(float value) {
+        int i = (int) (value + .5f);
+        if (i % 2 == 1) {
+            return i - 1;
+        }
+        return i;
     }
 
     public void setAddPaddingForCorners(boolean addPaddingForCorners) {
@@ -124,11 +134,11 @@
         if (shadowSize < 0 || maxShadowSize < 0) {
             throw new IllegalArgumentException("invalid shadow size");
         }
+        shadowSize = toEven(shadowSize);
+        maxShadowSize = toEven(maxShadowSize);
         if (shadowSize > maxShadowSize) {
             shadowSize = maxShadowSize;
             if (!mPrintedShadowClipWarning) {
-                Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
-                        + "{CardView#setMaxCardElevation}.");
                 mPrintedShadowClipWarning = true;
             }
         }
@@ -137,7 +147,7 @@
         }
         mRawShadowSize = shadowSize;
         mRawMaxShadowSize = maxShadowSize;
-        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
+        mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
         mMaxShadowSize = maxShadowSize + mInsetShadow;
         mDirty = true;
         invalidateSelf();
@@ -180,10 +190,11 @@
 
     @Override
     public int getOpacity() {
-        return PixelFormat.OPAQUE;
+        return PixelFormat.TRANSLUCENT;
     }
 
     void setCornerRadius(float radius) {
+        radius = (int) (radius + .5f);
         if (mCornerRadius == radius) {
             return;
         }
@@ -270,7 +281,6 @@
         // inner arc
         mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
         mCornerShadowPath.close();
-
         float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
         mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
@@ -284,15 +294,16 @@
                 -mCornerRadius - mShadowSize,
                 new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                 new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
+        mEdgeShadowPaint.setAntiAlias(false);
     }
 
     private void buildComponents(Rect bounds) {
         // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
         // We could have different top-bottom offsets to avoid extra gap above but in that case
         // center aligning Views inside the CardView would be problematic.
-        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
-        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
-                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
+        final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
+        mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
+                bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
         buildShadowCorners();
     }
 
@@ -332,7 +343,12 @@
         return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
     }
 
+    public void setColor(int color) {
+        mPaint.setColor(color);
+        invalidateSelf();
+    }
+
     static interface RoundRectHelper {
         void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
     }
-}
\ No newline at end of file
+}
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index a3ed369..bc992b8 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -225,6 +225,16 @@
     }
 
     /**
+     * Updates the background color of the CardView
+     *
+     * @param color The new color to set for the card background
+     * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
+     */
+    public void setCardBackgroundColor(int color) {
+        IMPL.setBackgroundColor(this, color);
+    }
+
+    /**
      * Returns the inner padding after the Card's left edge
      *
      * @return the inner padding after the Card's left edge
diff --git a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
index b5cb092..5369f86 100644
--- a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
+++ b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
@@ -31,7 +31,6 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
 import android.support.v7.gridlayout.R;
@@ -1491,7 +1490,11 @@
         equivalent to the single-source shortest paths problem on a digraph, for
         which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
         */
-        private void solve(Arc[] arcs, int[] locations) {
+        private boolean solve(Arc[] arcs, int[] locations) {
+            return solve(arcs, locations, true);
+        }
+
+        private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
             String axisName = horizontal ? "horizontal" : "vertical";
             int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
             boolean[] originalCulprits = null;
@@ -1509,10 +1512,14 @@
                         if (originalCulprits != null) {
                             logError(axisName, arcs, originalCulprits);
                         }
-                        return;
+                        return true;
                     }
                 }
 
+                if (!modifyOnError) {
+                    return false; // cannot solve with these constraints
+                }
+
                 boolean[] culprits = new boolean[arcs.length];
                 for (int i = 0; i < N; i++) {
                     for (int j = 0, length = arcs.length; j < length; j++) {
@@ -1536,6 +1543,7 @@
                     }
                 }
             }
+            return true;
         }
 
         private void computeMargins(boolean leading) {
@@ -1575,8 +1583,8 @@
             return trailingMargins;
         }
 
-        private void solve(int[] a) {
-            solve(getArcs(), a);
+        private boolean solve(int[] a) {
+            return solve(getArcs(), a);
         }
 
         private boolean computeHasWeights() {
@@ -1618,28 +1626,18 @@
             return deltas;
         }
 
-        private void shareOutDelta() {
-            int totalDelta = 0;
-            float totalWeight = 0;
+        private void shareOutDelta(int totalDelta, float totalWeight) {
+            Arrays.fill(deltas, 0);
             for (int i = 0, N = getChildCount(); i < N; i++) {
                 View c = getChildAt(i);
                 LayoutParams lp = getLayoutParams(c);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                 float weight = spec.weight;
                 if (weight != 0) {
-                    int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
-                    totalDelta += delta;
-                    totalWeight += weight;
-                }
-            }
-            for (int i = 0, N = getChildCount(); i < N; i++) {
-                LayoutParams lp = getLayoutParams(getChildAt(i));
-                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
-                float weight = spec.weight;
-                if (weight != 0) {
                     int delta = Math.round((weight * totalDelta / totalWeight));
                     deltas[i] = delta;
-                    // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+                    // the two adjustments below are to counter the above rounding and avoid
+                    // off-by-ones at the end
                     totalDelta -= delta;
                     totalWeight -= weight;
                 }
@@ -1649,12 +1647,46 @@
         private void solveAndDistributeSpace(int[] a) {
             Arrays.fill(getDeltas(), 0);
             solve(a);
-            shareOutDelta();
-            arcsValid = false;
-            forwardLinksValid = false;
-            backwardLinksValid = false;
-            groupBoundsValid = false;
-            solve(a);
+            int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
+            if (deltaMax < 2) {
+                return; //don't have any delta to distribute
+            }
+            int deltaMin = 0; //inclusive
+
+            float totalWeight = calculateTotalWeight();
+
+            int validDelta = -1; //delta for which a solution exists
+            boolean validSolution = true;
+            // do a binary search to find the max delta that won't conflict with constraints
+            while(deltaMin < deltaMax) {
+                final int delta = (deltaMin + deltaMax) / 2;
+                invalidateValues();
+                shareOutDelta(delta, totalWeight);
+                validSolution = solve(getArcs(), a, false);
+                if (validSolution) {
+                    validDelta = delta;
+                    deltaMin = delta + 1;
+                } else {
+                    deltaMax = delta;
+                }
+            }
+            if (validDelta > 0 && !validSolution) {
+                // last solution was not successful but we have a successful one. Use it.
+                invalidateValues();
+                shareOutDelta(validDelta, totalWeight);
+                solve(a);
+            }
+        }
+
+        private float calculateTotalWeight() {
+            float totalWeight = 0f;
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                totalWeight += spec.weight;
+            }
+            return totalWeight;
         }
 
         private void computeLocations(int[] a) {
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 9ad8c4a..a573954 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -48,7 +48,7 @@
 # A helper sub-library that makes direct use of JellyBean MR2 APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v7-mediarouter-jellybean-mr2
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 18
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr1
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
new file mode 100644
index 0000000..da91591
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..74b2d16
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
new file mode 100644
index 0000000..a74163a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..6e02527
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..26c6847
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..2fea78f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..2071e8f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..54a277e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png
new file mode 100644
index 0000000..fe26360
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_play.png b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
new file mode 100644
index 0000000..8afcd7d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..5b103e9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..0090701
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..41eb0b75
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..49f14cb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..5dd6aea
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..0d5ac5c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png
new file mode 100644
index 0000000..81c32fe
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png
new file mode 100644
index 0000000..864d8d2
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
new file mode 100644
index 0000000..568ae86
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_play_light.png b/v7/mediarouter/res/drawable-hdpi/ic_play_light.png
new file mode 100644
index 0000000..e278033
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
new file mode 100644
index 0000000..3248ad1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
new file mode 100644
index 0000000..c39fc1f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-hdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_audio_vol.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_audio_vol.png
index 2277170..17565c5 100644
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_audio_vol.png
+++ b/v7/mediarouter/res/drawable-hdpi/mr_ic_audio_vol.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index e215b96..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index a014e91..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index bb8bec1..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index aa1737e..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index 2c1434b..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index dbdce3e..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 1101864..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index e8e9069..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 8595158..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 14844d4..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 1565a29..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index 9b8fe87..0000000
--- a/v7/mediarouter/res/drawable-hdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
new file mode 100644
index 0000000..1121562
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..ebb616c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
new file mode 100644
index 0000000..7e5a3b6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..d6114dc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..76da308
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..25d3809
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..3d63b0d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..20e7619
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
new file mode 100644
index 0000000..7149f56
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_play.png b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
new file mode 100644
index 0000000..a1cb5df
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..01a0c50
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..8ec0cb1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..7cb6dcc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..38ee9f6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..91f1b4b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..6838f80
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png
new file mode 100644
index 0000000..c4218a4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png
new file mode 100644
index 0000000..ab26a30
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
new file mode 100644
index 0000000..4446faa
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_play_light.png b/v7/mediarouter/res/drawable-mdpi/ic_play_light.png
new file mode 100644
index 0000000..55b8c5e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
new file mode 100644
index 0000000..1f0ba42
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
new file mode 100644
index 0000000..3744fe4e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-mdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_audio_vol.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_audio_vol.png
index 4f8df78..36f079d 100644
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_audio_vol.png
+++ b/v7/mediarouter/res/drawable-mdpi/mr_ic_audio_vol.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 52e3a5a..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index 319c57e..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index f98c0a8..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index b74cdb5..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index a6a4bd0..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index 106fd3a..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 2c141ab..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 0b62d0b..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 23442b0..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 42b329f..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 58ff506..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index 25257f8..0000000
--- a/v7/mediarouter/res/drawable-mdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
new file mode 100644
index 0000000..e9a2a6f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..88978ff
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
new file mode 100644
index 0000000..30f413d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..4479d12
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..9f9b6d2
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..441b943
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..d8e59e6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..e20a895
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
new file mode 100644
index 0000000..90b6543
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
new file mode 100644
index 0000000..f615361
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..8882965
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..3f8b212
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..ab1b6ea
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..daa1310
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..7143236
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..6cfe6fb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png
new file mode 100644
index 0000000..bd7ec0f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png
new file mode 100644
index 0000000..e79d7d7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png
new file mode 100644
index 0000000..9a5d45f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png
new file mode 100644
index 0000000..acaeca6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
new file mode 100644
index 0000000..8db5dbb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
new file mode 100644
index 0000000..bfc30ef
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_audio_vol.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_audio_vol.png
index ea6bd43..e0dff4c 100644
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_audio_vol.png
+++ b/v7/mediarouter/res/drawable-xhdpi/mr_ic_audio_vol.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 4119cff..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index b629a57..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index fe81128..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index 9b59eaf..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index 1a513c1..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index ff78803..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index 4c4b624..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 60f8c4d..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index cdb2f30..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 97a10a3..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index a19a083..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index db30613..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
new file mode 100644
index 0000000..616bd2e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
new file mode 100644
index 0000000..ad30027
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
new file mode 100644
index 0000000..d30d4cf
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
new file mode 100644
index 0000000..9fd15ba
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
new file mode 100644
index 0000000..95ace86
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
new file mode 100644
index 0000000..ba85bea
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
new file mode 100644
index 0000000..8c448fa
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
new file mode 100644
index 0000000..7e93e4d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
new file mode 100644
index 0000000..6391830
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png
new file mode 100644
index 0000000..3b2d712
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
new file mode 100644
index 0000000..185ba25
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
new file mode 100644
index 0000000..3fa0ee6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
new file mode 100644
index 0000000..d21b07b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
new file mode 100644
index 0000000..063709e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
new file mode 100644
index 0000000..9f928ed
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
new file mode 100644
index 0000000..4355e79
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png
new file mode 100644
index 0000000..2a96557
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png
new file mode 100644
index 0000000..bf2560f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_pause_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png
new file mode 100644
index 0000000..1ec1995
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
new file mode 100644
index 0000000..a5bd8df
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
new file mode 100644
index 0000000..1d58233
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
new file mode 100644
index 0000000..43c9b99b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_setting_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_audio_vol.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_audio_vol.png
index 15b6311..3dd7a68 100755
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_audio_vol.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_audio_vol.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png
deleted file mode 100644
index 6fad4a6..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png
deleted file mode 100644
index 865617c..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png
deleted file mode 100644
index 44d98d5..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png
deleted file mode 100644
index b5b29b0..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_off_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png
deleted file mode 100644
index c807b50..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png
deleted file mode 100644
index 3fc7188..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_0_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png
deleted file mode 100644
index d54f44a..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png
deleted file mode 100644
index 092fe8c..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_1_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png
deleted file mode 100644
index 17c1d99..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png
deleted file mode 100644
index 4fd5808..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_2_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png
deleted file mode 100644
index 906401e..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png b/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png
deleted file mode 100644
index d29e563..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/mr_ic_media_route_on_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml
deleted file mode 100644
index aaa6473..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<animation-list
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:oneshot="false">
-    <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_light" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_light" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_light" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_light" android:duration="500" />
-</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
similarity index 67%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
copy to v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
index 6b27536..2d593c3 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
@@ -17,8 +17,8 @@
 <animation-list
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:oneshot="false">
-    <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
+    <item android:drawable="@drawable/ic_media_route_on_0_mono_dark" android:duration="500" />
+    <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
+    <item android:drawable="@drawable/ic_media_route_on_2_mono_dark" android:duration="500" />
+    <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
 </animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
similarity index 67%
rename from v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
rename to v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
index 6b27536..d495d99 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_holo_dark.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
@@ -17,8 +17,8 @@
 <animation-list
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:oneshot="false">
-    <item android:drawable="@drawable/mr_ic_media_route_on_0_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_2_holo_dark" android:duration="500" />
-    <item android:drawable="@drawable/mr_ic_media_route_on_1_holo_dark" android:duration="500" />
+    <item android:drawable="@drawable/ic_cast_on_0_light" android:duration="500" />
+    <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
+    <item android:drawable="@drawable/ic_cast_on_2_light" android:duration="500" />
+    <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
 </animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml
deleted file mode 100644
index 6870591..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_dark.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_dark" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_dark" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_dark" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_dark" />
-</selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
similarity index 79%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
index 0e4a065..6ae3b0b 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
@@ -16,10 +16,10 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
+            android:drawable="@drawable/ic_media_route_on_mono_dark" />
     <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
+            android:drawable="@drawable/mr_ic_media_route_connecting_mono_dark" />
     <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+            android:drawable="@drawable/ic_media_route_off_mono_dark" />
+    <item android:drawable="@drawable/ic_media_route_disabled_mono_dark" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
similarity index 79%
rename from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
rename to v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
index 0e4a065..a8cf6c8 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
@@ -16,10 +16,10 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
+            android:drawable="@drawable/ic_cast_on_light" />
     <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
+            android:drawable="@drawable/mr_ic_media_route_connecting_mono_light" />
     <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+            android:drawable="@drawable/ic_cast_off_light" />
+    <item android:drawable="@drawable/ic_cast_disabled_light" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
index 0e4a065..f3dc712 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_pause_dark.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_pause_dark" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_pause_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_pause_light.xml
index 0e4a065..9702be8 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_pause_light.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_pause_light" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_play_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_play_dark.xml
index 0e4a065..99e743c 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_play_dark.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_play_dark" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_play_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_play_light.xml
index 0e4a065..d18cc12 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_play_light.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_play_light" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
index 0e4a065..0fe662e 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_settings_dark.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_setting_dark" />
 </selector>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml b/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
similarity index 60%
copy from v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
copy to v7/mediarouter/res/drawable/mr_ic_settings_light.xml
index 0e4a065..a4614f6 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_holo_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_settings_light.xml
@@ -15,11 +15,5 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_on_holo_light" />
-    <item android:state_checkable="true" android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_connecting_holo_light" />
-    <item android:state_enabled="true"
-            android:drawable="@drawable/mr_ic_media_route_off_holo_light" />
-    <item android:drawable="@drawable/mr_ic_media_route_disabled_holo_light" />
+    <item android:drawable="@drawable/ic_setting_light" />
 </selector>
diff --git a/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml b/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml
deleted file mode 100644
index b45fd15..0000000
--- a/v7/mediarouter/res/layout-v11/mr_media_route_controller_dialog.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="fill_parent"
-              android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:divider="?android:attr/dividerHorizontal"
-              android:showDividers="middle">
-    <!-- Optional volume slider section. -->
-    <LinearLayout android:id="@+id/media_route_volume_layout"
-                  android:layout_width="fill_parent"
-                  android:layout_height="64dp"
-                  android:gravity="center_vertical"
-                  android:padding="8dp"
-                  android:visibility="gone">
-        <ImageView android:layout_width="48dp"
-                   android:layout_height="48dp"
-                   android:src="@drawable/mr_ic_audio_vol"
-                   android:gravity="center"
-                   android:scaleType="center" />
-        <SeekBar android:id="@+id/media_route_volume_slider"
-                 android:layout_width="0dp"
-                 android:layout_height="wrap_content"
-                 android:layout_weight="1"
-                 android:layout_marginLeft="8dp"
-                 android:layout_marginRight="8dp" />
-    </LinearLayout>
-
-    <!-- Optional content view section. -->
-    <FrameLayout android:id="@+id/media_route_control_frame"
-                 android:layout_width="fill_parent"
-                 android:layout_height="wrap_content"
-                 android:visibility="gone" />
-
-    <!-- Disconnect button. -->
-    <LinearLayout android:layout_width="fill_parent"
-                  android:layout_height="wrap_content"
-                  style="?attr/buttonBarStyle">
-        <Button android:id="@+id/media_route_disconnect_button"
-                android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
-                style="?attr/buttonBarButtonStyle"
-                android:gravity="center"
-                android:text="@string/mr_media_route_controller_disconnect" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml b/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml
deleted file mode 100644
index a1b24bd..0000000
--- a/v7/mediarouter/res/layout/mr_media_route_controller_dialog.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="fill_parent"
-              android:layout_height="wrap_content"
-              android:orientation="vertical"
-              android:divider="?android:attr/dividerHorizontal"
-              android:showDividers="middle">
-    <!-- Optional volume slider section. -->
-    <LinearLayout android:id="@+id/media_route_volume_layout"
-                  android:layout_width="fill_parent"
-                  android:layout_height="64dp"
-                  android:gravity="center_vertical"
-                  android:padding="8dp"
-                  android:visibility="gone">
-        <ImageView android:layout_width="48dp"
-                   android:layout_height="48dp"
-                   android:src="@drawable/mr_ic_audio_vol"
-                   android:gravity="center"
-                   android:scaleType="center" />
-        <SeekBar android:id="@+id/media_route_volume_slider"
-                 android:layout_width="0dp"
-                 android:layout_height="wrap_content"
-                 android:layout_weight="1"
-                 android:layout_marginLeft="8dp"
-                 android:layout_marginRight="8dp" />
-    </LinearLayout>
-
-    <!-- Optional content view section. -->
-    <FrameLayout android:id="@+id/media_route_control_frame"
-                 android:layout_width="fill_parent"
-                 android:layout_height="wrap_content"
-                 android:visibility="gone" />
-
-    <!-- Disconnect button. -->
-    <LinearLayout android:layout_width="fill_parent"
-                  android:layout_height="wrap_content"
-                  style="?attr/buttonBarStyle">
-        <View android:layout_width="0dp"
-              android:layout_height="0dp"
-              android:layout_weight="0.25" />
-        <Button android:id="@+id/media_route_disconnect_button"
-                android:layout_width="0dp"
-                android:layout_weight="1"
-                android:layout_height="fill_parent"
-                style="?attr/buttonBarButtonStyle"
-                android:gravity="center"
-                android:text="@string/mr_media_route_controller_disconnect" />
-        <View android:layout_width="0dp"
-              android:layout_height="0dp"
-              android:layout_weight="0.25" />
-    </LinearLayout>
-</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
new file mode 100644
index 0000000..3b12b24
--- /dev/null
+++ b/v7/mediarouter/res/layout/mr_media_route_controller_material_dialog_b.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+    <LinearLayout android:id="@+id/title_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+        <TextView android:id="@+id/route_name"
+                android:layout_width="0dp"
+                android:layout_height="72dp"
+                android:layout_weight="1"
+                android:layout_marginLeft="24dip"
+                android:layout_marginRight="24dip"
+                android:gravity="center_vertical"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textColor="?android:attr/textColorPrimary" />
+        <ImageButton android:id="@+id/settings"
+                android:layout_width="48dip"
+                android:layout_height="48dip"
+                android:padding="12dip"
+                android:layout_marginTop="12dip"
+                android:layout_marginBottom="12dip"
+                android:layout_marginRight="12dip"
+                android:contentDescription="@string/mr_media_route_controller_settings_description"
+                android:src="?attr/mediaRouteSettingsDrawable"
+                android:background="?attr/selectableItemBackgroundBorderless"
+                android:visibility="gone" />
+    </LinearLayout>
+    <FrameLayout android:id="@+id/media_route_control_frame"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" >
+        <RelativeLayout android:id="@+id/default_control_frame"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="?attr/colorPrimary" >
+            <ImageView android:id="@+id/art"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:maxHeight="@dimen/mr_media_route_controller_art_max_height"
+                    android:adjustViewBounds="true"
+                    android:scaleType="centerCrop"/>
+            <ImageButton android:id="@+id/play_pause"
+                    android:layout_width="48dip"
+                    android:layout_height="48dip"
+                    android:padding="12dip"
+                    android:layout_marginTop="8dip"
+                    android:layout_marginBottom="8dip"
+                    android:layout_alignParentRight="true"
+                    android:layout_below="@id/art"
+                    android:contentDescription="@string/mr_media_route_controller_play"
+                    android:background="?attr/selectableItemBackgroundBorderless"/>
+            <LinearLayout android:id="@+id/text_wrapper"
+                    android:orientation="vertical"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:minHeight="64dip"
+                    android:layout_marginLeft="24dip"
+                    android:gravity="center_vertical"
+                    android:layout_toLeftOf="@id/play_pause"
+                    android:layout_below="@id/art"
+                    android:layout_alignParentLeft="true" >
+                <TextView android:id="@+id/title"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="?android:attr/textAppearanceSmall"
+                        android:textColor="?android:attr/textColorPrimary"
+                        android:textSize="16sp"
+                        android:textStyle="bold"
+                        android:singleLine="true" />
+                <TextView android:id="@+id/subtitle"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="?android:attr/textAppearanceSmall"
+                        android:textColor="?android:attr/textColorPrimary"
+                        android:textSize="14sp"
+                        android:singleLine="true" />
+            </LinearLayout>
+        </RelativeLayout>
+    </FrameLayout>
+    <LinearLayout android:id="@+id/buttons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+        <Button android:id="@+id/disconnect"
+            android:layout_width="0dp"
+            android:layout_height="48dp"
+            android:gravity="center"
+            android:layout_weight="1"
+            android:background="?attr/selectableItemBackgroundBorderless"
+            android:text="@string/mr_media_route_controller_disconnect"
+            android:visibility="gone" />
+        <Button android:id="@+id/stop"
+            android:layout_width="0dp"
+            android:layout_height="48dp"
+            android:gravity="center"
+            android:layout_weight="1"
+            android:textColor="?attr/colorAccent"
+            android:background="?attr/selectableItemBackgroundBorderless"
+            android:text="@string/mr_media_route_controller_stop" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/v7/mediarouter/res/values-af/strings.xml b/v7/mediarouter/res/values-af/strings.xml
index e6b8027..0dcfa86 100644
--- a/v7/mediarouter/res/values-af/strings.xml
+++ b/v7/mediarouter/res/values-af/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koppel aan toestel"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Soek tans vir toestelle…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ontkoppel"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Hou op uitsaai"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Roete-instellings"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Speel"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Laat wag"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-am/strings.xml b/v7/mediarouter/res/values-am/strings.xml
index 5bbcea8..5d061c91 100644
--- a/v7/mediarouter/res/values-am/strings.xml
+++ b/v7/mediarouter/res/values-am/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ከመሳሪያ ጋር ያገናኙ"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"መሳሪያዎችን በመፈለግ ላይ…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ግንኙነት አቋርጥ"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"መውሰድ አቁም"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"የመንገድ ቅንብሮች"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"አጫውት"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ለአፍታ አቁም"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ar/strings.xml b/v7/mediarouter/res/values-ar/strings.xml
index dd04c47..ac0fb5d 100644
--- a/v7/mediarouter/res/values-ar/strings.xml
+++ b/v7/mediarouter/res/values-ar/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"الاتصال بجهاز"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"جارٍ البحث عن الأجهزة…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع الاتصال"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"إيقاف الإرسال"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"إعدادات المسار"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"تشغيل"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"إيقاف مؤقت"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-bg/strings.xml b/v7/mediarouter/res/values-bg/strings.xml
index d478516..0918332 100644
--- a/v7/mediarouter/res/values-bg/strings.xml
+++ b/v7/mediarouter/res/values-bg/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Свързване с устройство"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Търсят се устройства…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекратяване на връзката"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Спиране на предаването"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Настройки за маршрута"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Пускане"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Поставяне на пауза"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-bn-rBD/strings.xml b/v7/mediarouter/res/values-bn-rBD/strings.xml
index f1d1499..de862e5 100644
--- a/v7/mediarouter/res/values-bn-rBD/strings.xml
+++ b/v7/mediarouter/res/values-bn-rBD/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ডিভাইসে সংযোগ করুন"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ডিভাইসগুলি অনুসন্ধান করা হচ্ছে…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"সংযোগ বিচ্ছিন্ন করুন"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"কাস্ট করা বন্ধ করুন"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"সেটিংস রুট করুন"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"চালান"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"বিরাম দিন"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ca/strings.xml b/v7/mediarouter/res/values-ca/strings.xml
index 5c956a3..eac6632 100644
--- a/v7/mediarouter/res/values-ca/strings.xml
+++ b/v7/mediarouter/res/values-ca/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecta al dispositiu"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"S\'estan cercant dispositius…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconnecta"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Atura l\'emissió"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuració de la ruta"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reprodueix"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Posa en pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-cs/strings.xml b/v7/mediarouter/res/values-cs/strings.xml
index 014ac3c..111c02a 100644
--- a/v7/mediarouter/res/values-cs/strings.xml
+++ b/v7/mediarouter/res/values-cs/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Připojení k zařízení"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Vyhledávání zařízení…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojit"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ukončit odesílání"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavení trasy"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Přehrát"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pozastavit"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-da/strings.xml b/v7/mediarouter/res/values-da/strings.xml
index ca47a62..3b4fbf6 100644
--- a/v7/mediarouter/res/values-da/strings.xml
+++ b/v7/mediarouter/res/values-da/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Opret forbindelse til enheden"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søger efter enheder..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Afbryd forbindelsen"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop med at caste"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ruteindstillinger"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Afspil"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sæt på pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-de/strings.xml b/v7/mediarouter/res/values-de/strings.xml
index 098d4c0..5b8e494 100644
--- a/v7/mediarouter/res/values-de/strings.xml
+++ b/v7/mediarouter/res/values-de/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Mit Gerät verbinden"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Geräte werden gesucht…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbindung aufheben"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Übertragung stoppen"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Routingeinstellungen"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Wiedergabe"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-el/strings.xml b/v7/mediarouter/res/values-el/strings.xml
index f844adb..3640111 100644
--- a/v7/mediarouter/res/values-el/strings.xml
+++ b/v7/mediarouter/res/values-el/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Σύνδεση με τη συσκευή"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Αναζήτηση συσκευών…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Αποσύνδεση"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Διακοπή μετάδοσης"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ρυθμίσεις διαδρομής"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Αναπαραγωγή"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Παύση"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-en-rGB/strings.xml b/v7/mediarouter/res/values-en-rGB/strings.xml
index c16c294d..f5a8531 100644
--- a/v7/mediarouter/res/values-en-rGB/strings.xml
+++ b/v7/mediarouter/res/values-en-rGB/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop casting"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route settings"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Play"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-en-rIN/strings.xml b/v7/mediarouter/res/values-en-rIN/strings.xml
index c16c294d..f5a8531 100644
--- a/v7/mediarouter/res/values-en-rIN/strings.xml
+++ b/v7/mediarouter/res/values-en-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stop casting"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route settings"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Play"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-es-rUS/strings.xml b/v7/mediarouter/res/values-es-rUS/strings.xml
index 008c966..e1cf915 100644
--- a/v7/mediarouter/res/values-es-rUS/strings.xml
+++ b/v7/mediarouter/res/values-es-rUS/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar al dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Detener transmisión"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuración de ruta"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproducir"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausar"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-es/strings.xml b/v7/mediarouter/res/values-es/strings.xml
index d413fe1..0f2a8ea 100644
--- a/v7/mediarouter/res/values-es/strings.xml
+++ b/v7/mediarouter/res/values-es/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar a dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Dejar de enviar contenido"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ajustes de ruta"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproducir"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-et-rEE/strings.xml b/v7/mediarouter/res/values-et-rEE/strings.xml
index 6870211..ebc63fd 100644
--- a/v7/mediarouter/res/values-et-rEE/strings.xml
+++ b/v7/mediarouter/res/values-et-rEE/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Seadmega ühendamine"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Seadmete otsimine …"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkesta ühendus"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Lõpeta ülekanne"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Marsruudi seaded"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Esitamine"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Peatamine"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-eu-rES/strings.xml b/v7/mediarouter/res/values-eu-rES/strings.xml
index fb36265..d177a55 100644
--- a/v7/mediarouter/res/values-eu-rES/strings.xml
+++ b/v7/mediarouter/res/values-eu-rES/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Konektatu gailura"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Gailuak bilatzen…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deskonektatu"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Utzi igortzeari"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ibilbidearen ezarpenak"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Erreproduzitu"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausatu"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
index cb713a8..e094982 100644
--- a/v7/mediarouter/res/values-fa/strings.xml
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"برقراری ارتباط با دستگاه"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"در حال جستجو برای دستگاه‌ها..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع ارتباط"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"توقف فرستادن"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"تنظیمات مسیر"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"پخش"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"توقف موقت"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-fi/strings.xml b/v7/mediarouter/res/values-fi/strings.xml
index 86a79b1..a21dc91 100644
--- a/v7/mediarouter/res/values-fi/strings.xml
+++ b/v7/mediarouter/res/values-fi/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Yhdistä laitteeseen"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Etsitään laitteita…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkaise yhteys"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Lopeta suoratoisto"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Reitin asetukset"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Toista"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Keskeytä"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-fr-rCA/strings.xml b/v7/mediarouter/res/values-fr-rCA/strings.xml
index e1d6072..0655526 100644
--- a/v7/mediarouter/res/values-fr-rCA/strings.xml
+++ b/v7/mediarouter/res/values-fr-rCA/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connexion au périphérique"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Arrêter la diffusion"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Paramètres de l\'itinéraire"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Lecture"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Suspendre"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
index 59bfe66..9fce08a 100644
--- a/v7/mediarouter/res/values-fr/strings.xml
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecter à l\'appareil"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Arrêter la diffusion"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Paramètres de l\'itinéraire"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Lecture"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl-rES/strings.xml
index 51f980c..d1d73f9 100644
--- a/v7/mediarouter/res/values-gl-rES/strings.xml
+++ b/v7/mediarouter/res/values-gl-rES/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar co dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Parar de emitir"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configuración da ruta"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduce"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
index cce275e..6d100ea 100644
--- a/v7/mediarouter/res/values-hi/strings.xml
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"डिवाइस से कनेक्ट करें"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"डिवाइस की खोज हो रही है…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"डिस्कनेक्ट करें"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्ट करना बंद करें"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिंग"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"चलाएं"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"रोकें"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-hr/strings.xml b/v7/mediarouter/res/values-hr/strings.xml
index 91f8cd71..74e9270d 100644
--- a/v7/mediarouter/res/values-hr/strings.xml
+++ b/v7/mediarouter/res/values-hr/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezivanje s uređajem"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Traženje uređaja…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini vezu"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zaustavi emitiranje"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Postavke rute"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reprodukcija"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pauziraj"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-hu/strings.xml b/v7/mediarouter/res/values-hu/strings.xml
index bca65f4..efbc193 100644
--- a/v7/mediarouter/res/values-hu/strings.xml
+++ b/v7/mediarouter/res/values-hu/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Csatlakozás adott eszközhöz"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Eszközkeresés…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Leválasztás"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Átküldés leállítása"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Útvonal-beállítások"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Indítás"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Szüneteltetés"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
index 17058ab..faa6020 100644
--- a/v7/mediarouter/res/values-hy-rAM/strings.xml
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Միանալ սարքին"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Որոնվում են սարքեր..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Անջատել"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Դադարեցնել հեռարձակումը"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ֆայլերի փոխանցման կարգավորումներ"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Նվագարկել"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Դադար"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 578f696..e3123c1 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambungkan ke perangkat"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Menelusuri perangkat…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Hentikan transmisi"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Setelan rute"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Putar"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Jeda"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-is-rIS/strings.xml b/v7/mediarouter/res/values-is-rIS/strings.xml
index 436e7d2..262e4e9 100644
--- a/v7/mediarouter/res/values-is-rIS/strings.xml
+++ b/v7/mediarouter/res/values-is-rIS/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Tengjast tæki"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Leitar að tækjum…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Aftengja"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stöðva útsendingu"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Leiðarstillingar"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spila"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Hlé"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-it/strings.xml b/v7/mediarouter/res/values-it/strings.xml
index 973627e..bedd617 100644
--- a/v7/mediarouter/res/values-it/strings.xml
+++ b/v7/mediarouter/res/values-it/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connetti al dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ricerca di dispositivi…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnetti"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Interrompi trasmissione"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Impostazioni percorso"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Riproduci"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-iw/strings.xml b/v7/mediarouter/res/values-iw/strings.xml
index 233acbc..12d17b9 100644
--- a/v7/mediarouter/res/values-iw/strings.xml
+++ b/v7/mediarouter/res/values-iw/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"התחבר למכשיר"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"מחפש מכשירים…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"התנתק"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"עצור העברה"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"הגדרות נתיב"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"הפעל"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"השהה"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
index a89c22d..e97a65a 100644
--- a/v7/mediarouter/res/values-ja/strings.xml
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"端末に接続"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"端末を検索しています…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"接続を解除"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"キャストを停止"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ルーティング設定"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"再生"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"一時停止"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ka-rGE/strings.xml b/v7/mediarouter/res/values-ka-rGE/strings.xml
index f43e31e..758fe73 100644
--- a/v7/mediarouter/res/values-ka-rGE/strings.xml
+++ b/v7/mediarouter/res/values-ka-rGE/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"მოწყობილობასთან დაკავშირება"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"მოწყობილობების ძიება…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"კავშირის გაწყვეტა"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ტრანსლაციის შეჩერება"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"მარშრუტის პარამეტრები"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"დაკვრა"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"პაუზა"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-kk-rKZ/strings.xml b/v7/mediarouter/res/values-kk-rKZ/strings.xml
index 6344d71..c549a8c 100644
--- a/v7/mediarouter/res/values-kk-rKZ/strings.xml
+++ b/v7/mediarouter/res/values-kk-rKZ/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Құрылғыға жалғау"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Құрылғыларды іздеуде…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажырату"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Трансляциялауды тоқтату"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Жол параметрлері"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ойнату"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Кідірту"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-km-rKH/strings.xml b/v7/mediarouter/res/values-km-rKH/strings.xml
index d7cc49f..b3e53c5 100644
--- a/v7/mediarouter/res/values-km-rKH/strings.xml
+++ b/v7/mediarouter/res/values-km-rKH/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ភ្ជាប់​ឧបករណ៍"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"កំពុង​ស្វែងរក​ឧបករណ៍..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ផ្ដាច់"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"បញ្ឈប់ការខាស"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ការកំណត់ផ្លូវ"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ចាក់"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ផ្អាក"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-kn-rIN/strings.xml b/v7/mediarouter/res/values-kn-rIN/strings.xml
index 35a95af..36c3aaa 100644
--- a/v7/mediarouter/res/values-kn-rIN/strings.xml
+++ b/v7/mediarouter/res/values-kn-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ಸಾಧನಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಿ"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ಬಿತ್ತರಿಸುವಿಕೆ ನಿಲ್ಲಿಸು"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ಮಾರ್ಗ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ಪ್ಲೇ ಮಾಡು"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ವಿರಾಮ"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
index 8f1faa6..d165e52 100644
--- a/v7/mediarouter/res/values-ko/strings.xml
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"기기에 연결"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"기기 검색 중…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"연결 해제"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"전송 중지"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"경로 설정"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"재생"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"일시중지"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ky-rKG/strings.xml b/v7/mediarouter/res/values-ky-rKG/strings.xml
index 37ed974..1f7aba0 100644
--- a/v7/mediarouter/res/values-ky-rKG/strings.xml
+++ b/v7/mediarouter/res/values-ky-rKG/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Түзмөккө туташуу"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Түзмөктөр изделүүдө..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажыратуу"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Тышк экранга чыгарну токтотуу"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Багыт жөндөөлөрү"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ойнотуу"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Тындыруу"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-lo-rLA/strings.xml b/v7/mediarouter/res/values-lo-rLA/strings.xml
index 503ac98..6d61f7d 100644
--- a/v7/mediarouter/res/values-lo-rLA/strings.xml
+++ b/v7/mediarouter/res/values-lo-rLA/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ເຊື່ອມຕໍ່ຫາອຸປະກອນ"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ກຳລັງຊອກຫາອຸປະກອນ..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ຕັດການເຊື່ອມຕໍ່"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ຢຸດການສົ່ງສັນຍານ"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ການ​ຕັ້ງ​ຄ່າ​ເສັ້ນ​ທາງ"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ຫຼິ້ນ"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ຢຸດຊົ່ວຄາວ"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-lt/strings.xml b/v7/mediarouter/res/values-lt/strings.xml
index 6bc430b..2315618 100644
--- a/v7/mediarouter/res/values-lt/strings.xml
+++ b/v7/mediarouter/res/values-lt/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Prijungimas prie įrenginio"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ieškoma įrenginių…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atjungti"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Sustabdyti perdavimą"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Maršruto nustatymai"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Leisti"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pristabdyti"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-lv/strings.xml b/v7/mediarouter/res/values-lv/strings.xml
index a32e383..93e45de 100644
--- a/v7/mediarouter/res/values-lv/strings.xml
+++ b/v7/mediarouter/res/values-lv/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Savienojuma izveide ar ierīci"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Notiek ierīču meklēšana..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atvienot"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Pārtraukt apraidi"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Maršruta iestatījumi"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Atskaņot"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Apturēt"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-mk-rMK/strings.xml b/v7/mediarouter/res/values-mk-rMK/strings.xml
index 310f15b..9b3f875d 100644
--- a/v7/mediarouter/res/values-mk-rMK/strings.xml
+++ b/v7/mediarouter/res/values-mk-rMK/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Поврзи се со уредот"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Се пребаруваат уреди..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Исклучи се"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Запри префрлување"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Поставки на маршрутата"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Репродуцирај"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Пауза"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ml-rIN/strings.xml b/v7/mediarouter/res/values-ml-rIN/strings.xml
index 84bedd2..64c74b1 100644
--- a/v7/mediarouter/res/values-ml-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ml-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ഉപകരണത്തിലേക്ക് കണക്റ്റുചെയ്യുക"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ഉപകരണങ്ങൾക്കായി തിരയുന്നു…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"വിച്ഛേദിക്കുക"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"കാസ്റ്റുചെയ്യൽ നിർത്തുക"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"റൂട്ട് ക്രമീകരണം"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"പ്ലേ ചെയ്യുക"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"താൽക്കാലികമായി നിർത്തുക"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-mn-rMN/strings.xml b/v7/mediarouter/res/values-mn-rMN/strings.xml
index 6fbeef3..2074767 100644
--- a/v7/mediarouter/res/values-mn-rMN/strings.xml
+++ b/v7/mediarouter/res/values-mn-rMN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Төхөөрөмжтэй холбох"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Төхөөрөмжүүдийг хайж байна…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Салгах"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Нэвтрүүлэхийг зогсоох"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Маршрут тохиргоо"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Тоглуулах"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Түр зогсоох"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-mr-rIN/strings.xml b/v7/mediarouter/res/values-mr-rIN/strings.xml
index 62b81fe..bd020a7 100644
--- a/v7/mediarouter/res/values-mr-rIN/strings.xml
+++ b/v7/mediarouter/res/values-mr-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"डिव्हाइसला कनेक्ट करा"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"डिव्‍हाइसेस शोधत आहे…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"‍डिस्कनेक्ट करा"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्ट करणे थांबवा"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिंग्ज"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"प्ले करा"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"विराम द्या"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ms-rMY/strings.xml b/v7/mediarouter/res/values-ms-rMY/strings.xml
index 3e82660..05e9ffa 100644
--- a/v7/mediarouter/res/values-ms-rMY/strings.xml
+++ b/v7/mediarouter/res/values-ms-rMY/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambung kepada peranti"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Mencari peranti..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Berhenti menghantar"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Tetapan laluan"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Main"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Jeda"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-my-rMM/strings.xml b/v7/mediarouter/res/values-my-rMM/strings.xml
index f789bf06..20bfd8d 100644
--- a/v7/mediarouter/res/values-my-rMM/strings.xml
+++ b/v7/mediarouter/res/values-my-rMM/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"စက်တစ်ခုကို ချိတ်ဆက်ပါ"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"စက်ပစ္စည်းများကို ရှာဖွေနေပါသည်"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ချိတ်ဆက်ခြင်းရပ်တန့်ရန်"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ပုံစံသွင်းမှု ရပ်ရန်"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"လမ်းကြောင်း အပြင်အဆင်များ"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ဖွင့်ရန်"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"ခဏရပ်ရန်"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-nb/strings.xml b/v7/mediarouter/res/values-nb/strings.xml
index f7955a5..5ee8ec8 100644
--- a/v7/mediarouter/res/values-nb/strings.xml
+++ b/v7/mediarouter/res/values-nb/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koble til enheten"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søker etter enheter …"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koble fra"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Stopp castingen"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ruteinnstillinger"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spill av"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sett på pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ne-rNP/strings.xml b/v7/mediarouter/res/values-ne-rNP/strings.xml
index 0d521b9..aadcbcf 100644
--- a/v7/mediarouter/res/values-ne-rNP/strings.xml
+++ b/v7/mediarouter/res/values-ne-rNP/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"उपकरणसँग जडान गर्नुहोस्"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"उपकरणहरूका लागि खोजी गरिँदै..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"विच्छेदन गर्नुहोस्"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"कास्टिंग रोक्नुहोस्"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"मार्ग सेटिङ"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"बजाउनुहोस्"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"रोक्नुहोस्"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-nl/strings.xml b/v7/mediarouter/res/values-nl/strings.xml
index 934ff3b..fcfac4d 100644
--- a/v7/mediarouter/res/values-nl/strings.xml
+++ b/v7/mediarouter/res/values-nl/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Verbinding maken met apparaat"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Zoeken naar apparaten…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbinding verbreken"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Casten stoppen"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Route-instellingen"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Afspelen"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Onderbreken"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-pl/strings.xml b/v7/mediarouter/res/values-pl/strings.xml
index 21dc070..34fea86 100644
--- a/v7/mediarouter/res/values-pl/strings.xml
+++ b/v7/mediarouter/res/values-pl/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Połącz z urządzeniem"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Szukam urządzeń…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Rozłącz"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zakończ przesyłanie"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Ustawienia trasy"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Odtwórz"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Wstrzymaj"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-pt-rPT/strings.xml b/v7/mediarouter/res/values-pt-rPT/strings.xml
index c591eb3..1e1dbbb 100644
--- a/v7/mediarouter/res/values-pt-rPT/strings.xml
+++ b/v7/mediarouter/res/values-pt-rPT/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Ligar ao dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"A pesquisar dispositivos…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desassociar"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Parar a transmissão"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Definições de trajeto"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduzir"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Colocar em pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-pt/strings.xml b/v7/mediarouter/res/values-pt/strings.xml
index 31f2f27..67648a7 100644
--- a/v7/mediarouter/res/values-pt/strings.xml
+++ b/v7/mediarouter/res/values-pt/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar ao dispositivo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Procurando dispositivos…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Interromper transmissão"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Configurações de rota"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Reproduzir"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausar"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ro/strings.xml b/v7/mediarouter/res/values-ro/strings.xml
index e34f7ec..d738bab 100644
--- a/v7/mediarouter/res/values-ro/strings.xml
+++ b/v7/mediarouter/res/values-ro/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectați-vă la dispozitiv"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Se caută dispozitive..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deconectați-vă"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Nu mai proiectați"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Setări pentru traseu"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Redați"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Întrerupeți"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ru/strings.xml b/v7/mediarouter/res/values-ru/strings.xml
index 2d4fae4..dfa836c 100644
--- a/v7/mediarouter/res/values-ru/strings.xml
+++ b/v7/mediarouter/res/values-ru/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Подключение к устройству"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Поиск устройств…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Отключить"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Остановить трансляцию"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Настройки передачи файлов"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Воспроизвести."</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Приостановить."</string>
 </resources>
diff --git a/v7/mediarouter/res/values-si-rLK/strings.xml b/v7/mediarouter/res/values-si-rLK/strings.xml
index c22f92f..1ac7319 100644
--- a/v7/mediarouter/res/values-si-rLK/strings.xml
+++ b/v7/mediarouter/res/values-si-rLK/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"උපාංගයට සම්බන්ධ වන්න"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"උපාංග සඳහා සොයමින්…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"විසන්ධි කරන්න"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"කාස්ට් කිරීම නවත්වන්න"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"ගමන් මගේ සැකසීම්"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ධාවනය කරන්න"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"විරාමය"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sk/strings.xml b/v7/mediarouter/res/values-sk/strings.xml
index 76078f7..3156edf 100644
--- a/v7/mediarouter/res/values-sk/strings.xml
+++ b/v7/mediarouter/res/values-sk/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Pripojenie k zariadeniu"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Prebieha vyhľadávanie zariadení…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojiť"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Zastaviť prenášanie"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavenia trasy"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Prehrať"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pozastaviť"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sl/strings.xml b/v7/mediarouter/res/values-sl/strings.xml
index d642459..3de14aa 100644
--- a/v7/mediarouter/res/values-sl/strings.xml
+++ b/v7/mediarouter/res/values-sl/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezovanje z napravo"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iskanje naprav …"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini povezavo"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ustavi predvajanje"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Nastavitve poti"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Predvajaj"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Zaustavi"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sr/strings.xml b/v7/mediarouter/res/values-sr/strings.xml
index 6f26da9..de10685 100644
--- a/v7/mediarouter/res/values-sr/strings.xml
+++ b/v7/mediarouter/res/values-sr/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Повежите са уређајем"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Претраживање уређаја…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекини везу"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Заустави пребацивање"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Подешавања путање"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Пусти"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Паузирај"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sv/strings.xml b/v7/mediarouter/res/values-sv/strings.xml
index 750e68a..3ac428a 100644
--- a/v7/mediarouter/res/values-sv/strings.xml
+++ b/v7/mediarouter/res/values-sv/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Anslut till enhet"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Söker efter enheter ..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koppla från"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Sluta casta"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Inställningar för omdirigering"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Spela upp"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Pausa"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sw/strings.xml b/v7/mediarouter/res/values-sw/strings.xml
index 73df654..00ce337 100644
--- a/v7/mediarouter/res/values-sw/strings.xml
+++ b/v7/mediarouter/res/values-sw/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Unganisha kwenye kifaa"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Inatafuta vifaa..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Tenganisha"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Acha kutuma"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Mipangilio ya njia"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Google Play"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Sitisha"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-sw600dp/dimens.xml b/v7/mediarouter/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..5b29058
--- /dev/null
+++ b/v7/mediarouter/res/values-sw600dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="mr_media_route_controller_art_max_height">480dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values-ta-rIN/strings.xml b/v7/mediarouter/res/values-ta-rIN/strings.xml
index 14ab0e6..f92c432 100644
--- a/v7/mediarouter/res/values-ta-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ta-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"சாதனத்துடன் இணைக்கவும்"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"சாதனங்களைத் தேடுகிறது..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"துண்டி"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"அனுப்புவதை நிறுத்து"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"வழி அமைப்புகள்"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"இயக்கு"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"இடைநிறுத்து"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-te-rIN/strings.xml b/v7/mediarouter/res/values-te-rIN/strings.xml
index 2733225..0913420 100644
--- a/v7/mediarouter/res/values-te-rIN/strings.xml
+++ b/v7/mediarouter/res/values-te-rIN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"పరికరానికి కనెక్ట్ చేయండి"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"పరికరాల కోసం శోధిస్తోంది…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"డిస్‌కనెక్ట్ చేయి"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"ప్రసారాన్ని ఆపివేయి"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"మార్గ సెట్టింగ్‌లు"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"ప్లే చేయి"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"పాజ్ చేయి"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-th/strings.xml b/v7/mediarouter/res/values-th/strings.xml
index 236f4e9..31fc9c7 100644
--- a/v7/mediarouter/res/values-th/strings.xml
+++ b/v7/mediarouter/res/values-th/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"เชื่อมต่อกับอุปกรณ์"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"กำลังค้นหาอุปกรณ์…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ยกเลิกการเชื่อมต่อ"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"หยุดการส่ง"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"การตั้งค่าเส้นทาง"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"เล่น"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"หยุดชั่วคราว"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-tl/strings.xml b/v7/mediarouter/res/values-tl/strings.xml
index edc1236..d4896b5 100644
--- a/v7/mediarouter/res/values-tl/strings.xml
+++ b/v7/mediarouter/res/values-tl/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kumonekta sa device"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Naghahanap ng mga device…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Idiskonekta"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Itigil ang pagca-cast"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Mga setting ng ruta"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"I-play"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"I-pause"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
index 3106251..05344ff 100644
--- a/v7/mediarouter/res/values-tr/strings.xml
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Cihaza bağlanın"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Cihaz arayın…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Bağlantıyı kes"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Yayını durdur"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Rota ayarları"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Oynat"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Duraklat"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-uk/strings.xml b/v7/mediarouter/res/values-uk/strings.xml
index 110e445..b445b9c 100644
--- a/v7/mediarouter/res/values-uk/strings.xml
+++ b/v7/mediarouter/res/values-uk/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Під’єднатися до пристрою"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Пошук пристроїв…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Від’єднатися"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Зупинити трансляцію"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Налаштування маршруту"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Відтворити"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Призупинити"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-ur-rPK/strings.xml b/v7/mediarouter/res/values-ur-rPK/strings.xml
index 1ab69c5..e6ce4d6 100644
--- a/v7/mediarouter/res/values-ur-rPK/strings.xml
+++ b/v7/mediarouter/res/values-ur-rPK/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"آلہ سے مربوط ہوں"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"آلات تلاش کر رہا ہے…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"غیر مربوط کریں"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"کاسٹ کرنا بند کریں"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"روٹ کی ترتیبات"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"چلائیں"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"موقوف کریں"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-uz-rUZ/strings.xml b/v7/mediarouter/res/values-uz-rUZ/strings.xml
index 6d953cb..d2829ee 100644
--- a/v7/mediarouter/res/values-uz-rUZ/strings.xml
+++ b/v7/mediarouter/res/values-uz-rUZ/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Qurilmaga ulanish"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Qurilmalar izlanmoqda…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Uzish"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Translatsiyani to‘xtatish"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Yo‘naltirish sozlamalari"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Ijro qilish"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"To‘xtatib turish"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-vi/strings.xml b/v7/mediarouter/res/values-vi/strings.xml
index c6e107e..01ec106 100644
--- a/v7/mediarouter/res/values-vi/strings.xml
+++ b/v7/mediarouter/res/values-vi/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kết nối với thiết bị"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Đang tìm kiếm thiết bị…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ngắt kết nối"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Ngừng truyền"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Cài đặt tuyến đường"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Phát"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Tạm dừng"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-zh-rCN/strings.xml b/v7/mediarouter/res/values-zh-rCN/strings.xml
index 9750e6c..070f1de 100644
--- a/v7/mediarouter/res/values-zh-rCN/strings.xml
+++ b/v7/mediarouter/res/values-zh-rCN/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"连接到设备"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜索设备…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"断开连接"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投射"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由设置"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暂停"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-zh-rHK/strings.xml b/v7/mediarouter/res/values-zh-rHK/strings.xml
index e8ea767..a73d636 100644
--- a/v7/mediarouter/res/values-zh-rHK/strings.xml
+++ b/v7/mediarouter/res/values-zh-rHK/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投放"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由設定"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暫停"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-zh-rTW/strings.xml b/v7/mediarouter/res/values-zh-rTW/strings.xml
index 935f877..cb07c25 100644
--- a/v7/mediarouter/res/values-zh-rTW/strings.xml
+++ b/v7/mediarouter/res/values-zh-rTW/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置..."</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"停止投放"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"路由設定"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"播放"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"暫停"</string>
 </resources>
diff --git a/v7/mediarouter/res/values-zu/strings.xml b/v7/mediarouter/res/values-zu/strings.xml
index 3791584..24f0a37 100644
--- a/v7/mediarouter/res/values-zu/strings.xml
+++ b/v7/mediarouter/res/values-zu/strings.xml
@@ -22,4 +22,8 @@
     <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Xhumeka kudivayisi"</string>
     <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iseshela amadivayisi…"</string>
     <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Nqamula"</string>
+    <string name="mr_media_route_controller_stop" msgid="5398645111664294430">"Misa ukusakaza"</string>
+    <string name="mr_media_route_controller_settings_description" msgid="379358765881274425">"Izilungiselelo zomzila"</string>
+    <string name="mr_media_route_controller_play" msgid="5214423499524760404">"Dlala"</string>
+    <string name="mr_media_route_controller_pause" msgid="8315773974194466049">"Misa isikhashana"</string>
 </resources>
diff --git a/v7/mediarouter/res/values/attrs.xml b/v7/mediarouter/res/values/attrs.xml
index 2272e7a..5cd5606 100644
--- a/v7/mediarouter/res/values/attrs.xml
+++ b/v7/mediarouter/res/values/attrs.xml
@@ -30,4 +30,7 @@
     <attr name="mediaRouteOffDrawable" format="reference" />
     <attr name="mediaRouteConnectingDrawable" format="reference" />
     <attr name="mediaRouteOnDrawable" format="reference" />
+    <attr name="mediaRouteSettingsDrawable" format="reference" />
+    <attr name="mediaRoutePlayDrawable" format="reference" />
+    <attr name="mediaRoutePauseDrawable" format="reference" />
 </resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
new file mode 100644
index 0000000..e687c82
--- /dev/null
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="mr_media_route_controller_art_max_height">320dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/strings.xml b/v7/mediarouter/res/values/strings.xml
index b8944b6..c6eb0fb 100644
--- a/v7/mediarouter/res/values/strings.xml
+++ b/v7/mediarouter/res/values/strings.xml
@@ -34,4 +34,15 @@
     <!-- Button to disconnect from a media route.  [CHAR LIMIT=30] -->
     <string name="mr_media_route_controller_disconnect">Disconnect</string>
 
+    <!-- Button to stop playback and disconnect from a media route.  [CHAR LIMIT=30] -->
+    <string name="mr_media_route_controller_stop">Stop casting</string>
+
+    <!-- Description for a button that takes you to settings for the active route -->
+    <string name="mr_media_route_controller_settings_description">Route settings</string>
+
+    <!-- Accessibility description for the play button -->
+    <string name="mr_media_route_controller_play">Play</string>
+
+    <!-- Accessibility description for the pause button -->
+    <string name="mr_media_route_controller_pause">Pause</string>
 </resources>
diff --git a/v7/mediarouter/res/values/styles.xml b/v7/mediarouter/res/values/styles.xml
index fc6b411..9be8545 100644
--- a/v7/mediarouter/res/values/styles.xml
+++ b/v7/mediarouter/res/values/styles.xml
@@ -22,7 +22,7 @@
         <item name="android:padding">0dp</item>
         <item name="android:focusable">true</item>
         <item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
-        <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_holo_dark</item>
+        <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_dark</item>
     </style>
 
     <style name="Widget.MediaRouter.Light.MediaRouteButton"
@@ -32,6 +32,6 @@
         <item name="android:padding">0dp</item>
         <item name="android:focusable">true</item>
         <item name="android:contentDescription">@string/mr_media_route_button_content_description</item>
-        <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_holo_light</item>
+        <item name="externalRouteEnabledDrawable">@drawable/mr_ic_media_route_mono_light</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/v7/mediarouter/res/values/themes.xml b/v7/mediarouter/res/values/themes.xml
index 879188d..8350e04 100644
--- a/v7/mediarouter/res/values/themes.xml
+++ b/v7/mediarouter/res/values/themes.xml
@@ -19,17 +19,23 @@
     <style name="Theme.MediaRouter" parent="">
         <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
 
-        <item name="mediaRouteOffDrawable">@drawable/mr_ic_media_route_off_holo_dark</item>
-        <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_holo_dark</item>
-        <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_holo_dark</item>
+        <item name="mediaRouteOffDrawable">@drawable/ic_media_route_off_mono_dark</item>
+        <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_dark</item>
+        <item name="mediaRouteOnDrawable">@drawable/ic_media_route_on_mono_dark</item>
+        <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_dark</item>
+        <item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_dark</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_dark</item>
     </style>
 
     <style name="Theme.MediaRouter.Light" parent="">
         <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
 
-        <item name="mediaRouteOffDrawable">@drawable/mr_ic_media_route_off_holo_light</item>
-        <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_holo_light</item>
-        <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_holo_light</item>
+        <item name="mediaRouteOffDrawable">@drawable/ic_cast_off_light</item>
+        <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_light</item>
+        <item name="mediaRouteOnDrawable">@drawable/ic_cast_on_light</item>
+        <item name="mediaRouteSettingsDrawable">@drawable/mr_ic_settings_light</item>
+        <item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_light</item>
+        <item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_light</item>
     </style>
 
 </resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
index 3b14e2b..afbdc72 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
@@ -47,7 +47,7 @@
  * <h3>Prerequisites</h3>
  * <p>
  * To use the media route action provider, the activity must be a subclass of
- * {@link ActionBarActivity} from the <code>android.support.v7.appcompat</code>
+ * {@link AppCompatActivity} from the <code>android.support.v7.appcompat</code>
  * support library.  Refer to support library documentation for details.
  * </p>
  *
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index f5103fa..896c116 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -119,7 +119,7 @@
     }
 
     public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(MediaRouterThemeHelper.createThemedContext(context, false), attrs, defStyleAttr);
+        super(MediaRouterThemeHelper.createThemedContext(context), attrs, defStyleAttr);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 3a87f02..779ae8b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -62,7 +62,7 @@
     }
 
     public MediaRouteChooserDialog(Context context, int theme) {
-        super(MediaRouterThemeHelper.createThemedContext(context, true), theme);
+        super(MediaRouterThemeHelper.createThemedContext(context), theme);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 3fe9c78..b43af77 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -18,18 +18,29 @@
 
 import android.app.Dialog;
 import android.content.Context;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.mediarouter.R;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.Window;
 import android.widget.Button;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 /**
  * This class implements the route controller dialog for {@link MediaRouter}.
@@ -43,40 +54,49 @@
 public class MediaRouteControllerDialog extends Dialog {
     private static final String TAG = "MediaRouteControllerDialog";
 
-    // Time to wait before updating the volume when the user lets go of the seek bar
-    // to allow the route provider time to propagate the change and publish a new
-    // route descriptor.
-    private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
-
     private final MediaRouter mRouter;
     private final MediaRouterCallback mCallback;
     private final MediaRouter.RouteInfo mRoute;
 
     private boolean mCreated;
+    private boolean mAttachedToWindow;
     private Drawable mMediaRouteConnectingDrawable;
     private Drawable mMediaRouteOnDrawable;
     private Drawable mCurrentIconDrawable;
-
-    private boolean mVolumeControlEnabled = true;
-    private LinearLayout mVolumeLayout;
-    private SeekBar mVolumeSlider;
-    private boolean mVolumeSliderTouched;
+    private Drawable mSettingsDrawable;
 
     private View mControlView;
 
     private Button mDisconnectButton;
+    private Button mStopCastingButton;
+    private ImageButton mPlayPauseButton;
+    private ImageButton mSettingsButton;
+
+    private ImageView mArtView;
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private TextView mRouteNameView;
+    private View mTitlesWrapper;
+
+    private MediaControllerCompat mMediaController;
+    private MediaControllerCallback mControllerCallback;
+    private PlaybackStateCompat mState;
+    private MediaDescriptionCompat mDescription;
+
 
     public MediaRouteControllerDialog(Context context) {
         this(context, 0);
     }
 
     public MediaRouteControllerDialog(Context context, int theme) {
-        super(MediaRouterThemeHelper.createThemedContext(context, true), theme);
+        super(MediaRouterThemeHelper.createThemedContext(context), theme);
         context = getContext();
 
+        mControllerCallback = new MediaControllerCallback();
         mRouter = MediaRouter.getInstance(context);
         mCallback = new MediaRouterCallback();
         mRoute = mRouter.getSelectedRoute();
+        setMediaSession(mRouter.getMediaSessionToken());
     }
 
     /**
@@ -108,85 +128,73 @@
     }
 
     /**
-     * Sets whether to enable the volume slider and volume control using the volume keys
-     * when the route supports it.
-     * <p>
-     * The default value is true.
-     * </p>
+     * Set the session to use for metadata and transport controls. The dialog
+     * will listen to changes on this session and update the UI automatically in
+     * response to changes.
+     *
+     * @param sessionToken The token for the session to use.
      */
-    public void setVolumeControlEnabled(boolean enable) {
-        if (mVolumeControlEnabled != enable) {
-            mVolumeControlEnabled = enable;
-            if (mCreated) {
-                updateVolume();
-            }
+    private void setMediaSession(MediaSessionCompat.Token sessionToken) {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mControllerCallback);
+            mMediaController = null;
         }
+        if (sessionToken == null) {
+            return;
+        }
+        if (!mAttachedToWindow) {
+            return;
+        }
+        try {
+            mMediaController = new MediaControllerCompat(getContext(), sessionToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error creating media controller in setMediaSession.", e);
+        }
+        if (mMediaController != null) {
+            mMediaController.registerCallback(mControllerCallback);
+        }
+        MediaMetadataCompat metadata = mMediaController == null ? null
+                : mMediaController.getMetadata();
+        mDescription = metadata == null ? null : metadata.getDescription();
+        mState = mMediaController == null ? null : mMediaController.getPlaybackState();
+        update();
     }
 
     /**
-     * Returns whether to enable the volume slider and volume control using the volume keys
-     * when the route supports it.
+     * Gets the description being used by the default UI.
+     *
+     * @return The current description.
      */
-    public boolean isVolumeControlEnabled() {
-        return mVolumeControlEnabled;
+    public MediaSessionCompat.Token getMediaSession() {
+        return mMediaController == null ? null : mMediaController.getSessionToken();
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
 
-        setContentView(R.layout.mr_media_route_controller_dialog);
+        setContentView(R.layout.mr_media_route_controller_material_dialog_b);
 
-        mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout);
-        mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider);
-        mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            private final Runnable mStopTrackingTouch = new Runnable() {
-                @Override
-                public void run() {
-                    if (mVolumeSliderTouched) {
-                        mVolumeSliderTouched = false;
-                        updateVolume();
-                    }
-                }
-            };
+        ClickListener listener = new ClickListener();
 
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-                if (mVolumeSliderTouched) {
-                    mVolumeSlider.removeCallbacks(mStopTrackingTouch);
-                } else {
-                    mVolumeSliderTouched = true;
-                }
-            }
+        mDisconnectButton = (Button) findViewById(R.id.disconnect);
+        mDisconnectButton.setOnClickListener(listener);
 
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-                // Defer resetting mVolumeSliderTouched to allow the media route provider
-                // a little time to settle into its new state and publish the final
-                // volume update.
-                mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
-            }
+        mStopCastingButton = (Button) findViewById(R.id.stop);
+        mStopCastingButton.setOnClickListener(listener);
 
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (fromUser) {
-                    mRoute.requestSetVolume(progress);
-                }
-            }
-        });
+        mSettingsButton = (ImageButton) findViewById(R.id.settings);
+        mSettingsButton.setOnClickListener(listener);
 
-        mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button);
-        mDisconnectButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if (mRoute.isSelected()) {
-                    mRouter.getDefaultRoute().select();
-                }
-                dismiss();
-            }
-        });
+        mArtView = (ImageView) findViewById(R.id.art);
+        mTitleView = (TextView) findViewById(R.id.title);
+        mSubtitleView = (TextView) findViewById(R.id.subtitle);
+        mTitlesWrapper = findViewById(R.id.text_wrapper);
+        mPlayPauseButton = (ImageButton) findViewById(R.id.play_pause);
+        mPlayPauseButton.setOnClickListener(listener);
+        mRouteNameView = (TextView) findViewById(R.id.route_name);
 
         mCreated = true;
         if (update()) {
@@ -194,28 +202,27 @@
             FrameLayout controlFrame =
                     (FrameLayout)findViewById(R.id.media_route_control_frame);
             if (mControlView != null) {
+                controlFrame.findViewById(R.id.default_control_frame).setVisibility(View.GONE);
                 controlFrame.addView(mControlView);
-                controlFrame.setVisibility(View.VISIBLE);
-            } else {
-                controlFrame.setVisibility(View.GONE);
             }
         }
     }
 
-
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
+        mAttachedToWindow = true;
 
         mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
                 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
-        update();
+        setMediaSession(mRouter.getMediaSessionToken());
     }
 
     @Override
     public void onDetachedFromWindow() {
         mRouter.removeCallback(mCallback);
-
+        setMediaSession(null);
+        mAttachedToWindow = false;
         super.onDetachedFromWindow();
     }
 
@@ -243,20 +250,89 @@
             dismiss();
             return false;
         }
+        if (!mCreated) {
+            return false;
+        }
 
-        setTitle(mRoute.getName());
-        updateVolume();
+        mRouteNameView.setText(mRoute.getName());
 
-        Drawable icon = getIconDrawable();
-        if (icon != mCurrentIconDrawable) {
-            mCurrentIconDrawable = icon;
+        if (mRoute.canDisconnect()) {
+            mDisconnectButton.setVisibility(View.VISIBLE);
+        } else {
+            mDisconnectButton.setVisibility(View.GONE);
+        }
 
-            // Prior to KLP MR1 there was a bug in ImageView that caused feature drawables
-            // to not start animating unless they experienced a transition from
-            // invisible to visible.  So we force the drawable to be invisible here.
-            // The window will make the drawable visible when attached.
-            icon.setVisible(false, true);
-            getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon);
+        if (mRoute.getSettingsIntent() != null) {
+            mSettingsButton.setVisibility(View.VISIBLE);
+        } else {
+            mSettingsButton.setVisibility(View.GONE);
+        }
+
+        if (mControlView == null) {
+            if (mDescription != null) {
+                if (mDescription.getIconBitmap() != null) {
+                    mArtView.setImageBitmap(mDescription.getIconBitmap());
+                    mArtView.setVisibility(View.VISIBLE);
+                } else if (mDescription.getIconUri() != null) {
+                    // TODO replace with background load of icon
+                    mArtView.setImageURI(mDescription.getIconUri());
+                    mArtView.setVisibility(View.VISIBLE);
+                } else {
+                    mArtView.setImageDrawable(null);
+                    mArtView.setVisibility(View.GONE);
+                }
+
+                boolean haveText = false;
+                CharSequence text = mDescription.getTitle();
+                if (!TextUtils.isEmpty(text)) {
+                    mTitleView.setText(text);
+                    haveText = true;
+                } else {
+                    mTitleView.setText(null);
+                    mTitleView.setVisibility(View.GONE);
+                }
+                text = mDescription.getSubtitle();
+                if (!TextUtils.isEmpty(text)) {
+                    mSubtitleView.setText(mDescription.getSubtitle());
+                    haveText = true;
+                } else {
+                    mSubtitleView.setText(null);
+                    mSubtitleView.setVisibility(View.GONE);
+                }
+                if (!haveText) {
+                    mTitlesWrapper.setVisibility(View.GONE);
+                } else {
+                    mTitlesWrapper.setVisibility(View.VISIBLE);
+                }
+            } else {
+                mArtView.setVisibility(View.GONE);
+                mTitlesWrapper.setVisibility(View.GONE);
+            }
+            if (mState != null) {
+                boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
+                        || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
+                boolean supportsPlay = (mState.getActions() & (PlaybackStateCompat.ACTION_PLAY
+                        | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+                boolean supportsPause = (mState.getActions() & (PlaybackStateCompat.ACTION_PAUSE
+                                | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+                if (isPlaying && supportsPause) {
+                    mPlayPauseButton.setVisibility(View.VISIBLE);
+                    mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
+                            getContext(), R.attr.mediaRoutePauseDrawable));
+                    mPlayPauseButton.setContentDescription(getContext().getResources()
+                            .getText(R.string.mr_media_route_controller_pause));
+                } else if (!isPlaying && supportsPlay) {
+                    mPlayPauseButton.setVisibility(View.VISIBLE);
+                    mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
+                            getContext(), R.attr.mediaRoutePlayDrawable));
+                    mPlayPauseButton.setContentDescription(getContext().getResources()
+                            .getText(R.string.mr_media_route_controller_play));
+                } else {
+                    mPlayPauseButton.setVisibility(View.GONE);
+                }
+            } else {
+                mPlayPauseButton.setVisibility(View.GONE);
+            }
         }
         return true;
     }
@@ -277,23 +353,6 @@
         }
     }
 
-    private void updateVolume() {
-        if (!mVolumeSliderTouched) {
-            if (isVolumeControlAvailable()) {
-                mVolumeLayout.setVisibility(View.VISIBLE);
-                mVolumeSlider.setMax(mRoute.getVolumeMax());
-                mVolumeSlider.setProgress(mRoute.getVolume());
-            } else {
-                mVolumeLayout.setVisibility(View.GONE);
-            }
-        }
-    }
-
-    private boolean isVolumeControlAvailable() {
-        return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
-                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
-    }
-
     private final class MediaRouterCallback extends MediaRouter.Callback {
         @Override
         public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
@@ -308,7 +367,61 @@
         @Override
         public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
             if (route == mRoute) {
-                updateVolume();
+            }
+        }
+    }
+
+    private final class MediaControllerCallback extends MediaControllerCompat.Callback {
+        @Override
+        public void onSessionDestroyed() {
+            if (mMediaController != null) {
+                mMediaController.unregisterCallback(mControllerCallback);
+                mMediaController = null;
+            }
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            mState = state;
+            update();
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            mDescription = metadata == null ? null : metadata.getDescription();
+            update();
+        }
+    }
+
+    private final class ClickListener implements View.OnClickListener {
+        @Override
+        public void onClick(View v) {
+            int id = v.getId();
+            if (id == R.id.stop || id == R.id.disconnect) {
+                if (mRoute.isSelected()) {
+                    mRouter.unselect(id == R.id.stop ?
+                            MediaRouter.UNSELECT_REASON_STOPPED :
+                            MediaRouter.UNSELECT_REASON_DISCONNECTED);
+                }
+                dismiss();
+            } else if (id == R.id.play_pause) {
+                if (mMediaController != null && mState != null) {
+                    if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) {
+                        mMediaController.getTransportControls().pause();
+                    } else {
+                        mMediaController.getTransportControls().play();
+                    }
+                }
+            } else if (id == R.id.settings) {
+                IntentSender is = mRoute.getSettingsIntent();
+                if (is != null) {
+                    try {
+                        is.sendIntent(null, 0, null, null, null);
+                        dismiss();
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error opening route settings.", e);
+                    }
+                }
             }
         }
     }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 1fb9346..09999a1 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -26,12 +26,8 @@
     private MediaRouterThemeHelper() {
     }
 
-    public static Context createThemedContext(Context context, boolean forceDark) {
+    public static Context createThemedContext(Context context) {
         boolean isLightTheme = isLightTheme(context);
-        if (isLightTheme && forceDark) {
-            context = new ContextThemeWrapper(context, R.style.Theme_AppCompat);
-            isLightTheme = false;
-        }
         return new ContextThemeWrapper(context, isLightTheme ?
                 R.style.Theme_MediaRouter_Light : R.style.Theme_MediaRouter);
     }
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
index 7ccffe1..d83ad2f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
@@ -16,6 +16,7 @@
 package android.support.v7.media;
 
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.os.Bundle;
 import android.text.TextUtils;
 
@@ -48,6 +49,8 @@
     private static final String KEY_VOLUME_HANDLING = "volumeHandling";
     private static final String KEY_PRESENTATION_DISPLAY_ID = "presentationDisplayId";
     private static final String KEY_EXTRAS = "extras";
+    private static final String KEY_CAN_DISCONNECT = "canDisconnect";
+    private static final String KEY_SETTINGS_INTENT = "settingsIntent";
 
     private final Bundle mBundle;
     private List<IntentFilter> mControlFilters;
@@ -106,6 +109,27 @@
     }
 
     /**
+     * Gets whether the route can be disconnected without stopping playback. To
+     * specify that the route should disconnect without stopping use
+     * {@link MediaRouter#unselect(int)} with
+     * {@link MediaRouter#UNSELECT_REASON_DISCONNECTED}.
+     */
+    public boolean canDisconnectAndKeepPlaying() {
+        return mBundle.getBoolean(KEY_CAN_DISCONNECT, false);
+    }
+
+    /**
+     * Gets an {@link IntentSender} for starting a settings activity for this
+     * route. The activity may have specific route settings or general settings
+     * for the connected device or route provider.
+     *
+     * @return An {@link IntentSender} to start a settings activity.
+     */
+    public IntentSender getSettingsActivity() {
+        return mBundle.getParcelable(KEY_SETTINGS_INTENT);
+    }
+
+    /**
      * Gets the route's {@link MediaControlIntent media control intent} filters.
      */
     public List<IntentFilter> getControlFilters() {
@@ -323,6 +347,23 @@
         }
 
         /**
+         * Sets whether the route can be disconnected without stopping playback.
+         */
+        public Builder setCanDisconnect(boolean canDisconnect) {
+            mBundle.putBoolean(KEY_CAN_DISCONNECT, canDisconnect);
+            return this;
+        }
+
+        /**
+         * Sets an intent sender for launching the settings activity for this
+         * route.
+         */
+        public Builder setSettingsActivity(IntentSender is) {
+            mBundle.putParcelable(KEY_SETTINGS_INTENT, is);
+            return this;
+        }
+
+        /**
          * Adds a {@link MediaControlIntent media control intent} filter for the route.
          */
         public Builder addControlFilter(IntentFilter filter) {
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index e011877..f370e95 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -331,6 +331,24 @@
         }
 
         /**
+         * Unselects the route and provides a reason. The default implementation
+         * calls {@link #onUnselect()}.
+         * <p>
+         * The reason provided will be one of the following:
+         * <ul>
+         * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+         * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+         * </ul>
+         *
+         * @param reason The reason for unselecting the route.
+         */
+        public void onUnselect(int reason) {
+            onUnselect();
+        }
+
+        /**
          * Requests to set the volume of the route.
          *
          * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
index f44dcac..3f0976f 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderProtocol.java
@@ -121,6 +121,7 @@
 
     public static final String CLIENT_DATA_ROUTE_ID = "routeId";
     public static final String CLIENT_DATA_VOLUME = "volume";
+    public static final String CLIENT_DATA_UNSELECT_REASON = "unselectReason";
 
     /*
      * Messages sent from the service to the client.
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index d3f548d..79d87e1 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -234,13 +234,13 @@
     }
 
     private boolean onUnselectRoute(Messenger messenger, int requestId,
-            int controllerId) {
+            int controllerId, int reason) {
         ClientRecord client = getClient(messenger);
         if (client != null) {
             MediaRouteProvider.RouteController controller =
                     client.getRouteController(controllerId);
             if (controller != null) {
-                controller.onUnselect();
+                controller.onUnselect(reason);
                 if (DEBUG) {
                     Log.d(TAG, client + ": Route unselected"
                             + ", controllerId=" + controllerId);
@@ -633,7 +633,11 @@
                         return service.onSelectRoute(messenger, requestId, arg);
 
                     case CLIENT_MSG_UNSELECT_ROUTE:
-                        return service.onUnselectRoute(messenger, requestId, arg);
+                        int reason = data == null ?
+                                MediaRouter.UNSELECT_REASON_UNKNOWN
+                                : data.getInt(CLIENT_DATA_UNSELECT_REASON,
+                                        MediaRouter.UNSELECT_REASON_UNKNOWN);
+                        return service.onUnselectRoute(messenger, requestId, arg, reason);
 
                     case CLIENT_MSG_SET_ROUTE_VOLUME: {
                         int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 9169b6b..5bb998e 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -17,11 +17,13 @@
 package android.support.v7.media;
 
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.os.Bundle;
@@ -69,6 +71,30 @@
     private static final String TAG = "MediaRouter";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * when the reason the route was unselected is unknown.
+     */
+    public static final int UNSELECT_REASON_UNKNOWN = 0;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * when the user pressed the disconnect button to disconnect and keep playing.
+     * <p>
+     *
+     * @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}.
+     */
+    public static final int UNSELECT_REASON_DISCONNECTED = 1;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * when the user pressed the stop casting button.
+     */
+    public static final int UNSELECT_REASON_STOPPED = 2;
+    /**
+     * Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
+     * when the user selected a different route.
+     */
+    public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
+
     // Maintains global media router state for the process.
     // This field is initialized in MediaRouter.getInstance() before any
     // MediaRouter objects are instantiated so it is guaranteed to be
@@ -354,13 +380,37 @@
     }
 
     /**
+     * Unselects the current round and selects the default route instead.
+     * <p>
+     * The reason given must be one of:
+     * <ul>
+     * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+     * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+     * </ul>
+     *
+     * @param reason The reason for disconnecting the current route.
+     */
+    public void unselect(int reason) {
+        if (reason < MediaRouter.UNSELECT_REASON_UNKNOWN ||
+                reason > MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) {
+            throw new IllegalArgumentException("Unsupported reason to unselect route");
+        }
+        checkCallingThread();
+
+        sGlobal.selectRoute(getDefaultRoute(), reason);
+    }
+
+    /**
      * Returns true if there is a route that matches the specified selector.
      * <p>
-     * This method returns true if there are any available routes that match the selector
-     * regardless of whether they are enabled or disabled.  If the
+     * This method returns true if there are any available routes that match the
+     * selector regardless of whether they are enabled or disabled. If the
      * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
      * the method will only consider non-default routes.
-     * </p><p class="note">
+     * </p>
+     * <p class="note">
      * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
      * will return true if it is possible to discover a matching route even if
      * discovery is not in progress or if no matching route has yet been found.
@@ -368,9 +418,10 @@
      * </p>
      *
      * @param selector The selector to match.
-     * @param flags Flags to control the determination of whether a route may be available.
-     * May be zero or some combination of {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}
-     * and {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
+     * @param flags Flags to control the determination of whether a route may be
+     *            available. May be zero or some combination of
+     *            {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} and
+     *            {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
      * @return True if a matching route may be available.
      */
     public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
@@ -670,6 +721,25 @@
     }
 
     /**
+     * Sets a compat media session to enable remote control of the volume of the
+     * selected route. This should be used instead of
+     * {@link #addRemoteControlClient} when using {@link MediaSessionCompat}.
+     * Set the session to null to clear it.
+     *
+     * @param mediaSession
+     */
+    public void setMediaSessionCompat(MediaSessionCompat mediaSession) {
+        if (DEBUG) {
+            Log.d(TAG, "addMediaSessionCompat: " + mediaSession);
+        }
+        sGlobal.setMediaSessionCompat(mediaSession);
+    }
+
+    public MediaSessionCompat.Token getMediaSessionToken() {
+        return sGlobal.getMediaSessionToken();
+    }
+
+    /**
      * Ensures that calls into the media router are on the correct thread.
      * It pays to be a little paranoid when global state invariants are at risk.
      */
@@ -700,6 +770,7 @@
         private String mDescription;
         private boolean mEnabled;
         private boolean mConnecting;
+        private boolean mCanDisconnect;
         private final ArrayList<IntentFilter> mControlFilters = new ArrayList<IntentFilter>();
         private int mPlaybackType;
         private int mPlaybackStream;
@@ -709,6 +780,7 @@
         private Display mPresentationDisplay;
         private int mPresentationDisplayId = -1;
         private Bundle mExtras;
+        private IntentSender mSettingsIntent;
         private MediaRouteDescriptor mDescriptor;
 
         /** @hide */
@@ -1072,6 +1144,17 @@
         }
 
         /**
+         * Gets whether this route supports disconnecting without interrupting
+         * playback.
+         *
+         * @return True if this route can disconnect without stopping playback,
+         *         false otherwise.
+         */
+        public boolean canDisconnect() {
+            return mCanDisconnect;
+        }
+
+        /**
          * Requests a volume change for this route asynchronously.
          * <p>
          * This function may only be called on a selected route.  It will have
@@ -1149,6 +1232,15 @@
         }
 
         /**
+         * Gets an intent sender for launching a settings activity for this
+         * route.
+         */
+        @Nullable
+        public IntentSender getSettingsIntent() {
+            return mSettingsIntent;
+        }
+
+        /**
          * Selects this media route.
          */
         public void select() {
@@ -1163,6 +1255,7 @@
                     + ", description=" + mDescription
                     + ", enabled=" + mEnabled
                     + ", connecting=" + mConnecting
+                    + ", canDisconnect=" + mCanDisconnect
                     + ", playbackType=" + mPlaybackType
                     + ", playbackStream=" + mPlaybackStream
                     + ", volumeHandling=" + mVolumeHandling
@@ -1170,6 +1263,7 @@
                     + ", volumeMax=" + mVolumeMax
                     + ", presentationDisplayId=" + mPresentationDisplayId
                     + ", extras=" + mExtras
+                    + ", settingsIntent=" + mSettingsIntent
                     + ", providerPackageName=" + mProvider.getPackageName()
                     + " }";
         }
@@ -1229,6 +1323,14 @@
                         mExtras = descriptor.getExtras();
                         changes |= CHANGE_GENERAL;
                     }
+                    if (!equal(mSettingsIntent, descriptor.getSettingsActivity())) {
+                        mSettingsIntent = descriptor.getSettingsActivity();
+                        changes |= CHANGE_GENERAL;
+                    }
+                    if (mCanDisconnect != descriptor.canDisconnectAndKeepPlaying()) {
+                        mCanDisconnect = descriptor.canDisconnectAndKeepPlaying();
+                        changes |= CHANGE_GENERAL | CHANGE_PRESENTATION_DISPLAY;
+                    }
                 }
             }
             return changes;
@@ -1521,6 +1623,21 @@
         private MediaRouteProvider.RouteController mSelectedRouteController;
         private MediaRouteDiscoveryRequest mDiscoveryRequest;
         private MediaSessionRecord mMediaSession;
+        private MediaSessionCompat mRccMediaSession;
+        private MediaSessionCompat mCompatSession;
+        private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+                new MediaSessionCompat.OnActiveChangeListener() {
+            @Override
+            public void onActiveChanged() {
+                if(mRccMediaSession != null) {
+                    if (mRccMediaSession.isActive()) {
+                        addRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    } else {
+                        removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    }
+                }
+            }
+        };
 
         GlobalMediaRouter(Context applicationContext) {
             mApplicationContext = applicationContext;
@@ -1634,6 +1751,10 @@
         }
 
         public void selectRoute(RouteInfo route) {
+            selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+        }
+
+        public void selectRoute(RouteInfo route, int unselectReason) {
             if (!mRoutes.contains(route)) {
                 Log.w(TAG, "Ignoring attempt to select removed route: " + route);
                 return;
@@ -1643,7 +1764,7 @@
                 return;
             }
 
-            setSelectedRouteInternal(route);
+            setSelectedRouteInternal(route, unselectReason);
         }
 
         public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
@@ -1952,13 +2073,15 @@
             if (mSelectedRoute != null && !isRouteSelectable(mSelectedRoute)) {
                 Log.i(TAG, "Unselecting the current route because it "
                         + "is no longer selectable: " + mSelectedRoute);
-                setSelectedRouteInternal(null);
+                setSelectedRouteInternal(null,
+                        MediaRouter.UNSELECT_REASON_UNKNOWN);
             }
             if (mSelectedRoute == null) {
                 // Choose a new route.
                 // This will have the side-effect of updating the playback info when
                 // the new route is selected.
-                setSelectedRouteInternal(chooseFallbackRoute());
+                setSelectedRouteInternal(chooseFallbackRoute(),
+                        MediaRouter.UNSELECT_REASON_UNKNOWN);
             } else if (selectedRouteDescriptorChanged) {
                 // Update the playback info because the properties of the route have changed.
                 updatePlaybackInfoFromSelectedRoute();
@@ -1998,15 +2121,16 @@
                             SystemMediaRouteProvider.DEFAULT_ROUTE_ID);
         }
 
-        private void setSelectedRouteInternal(RouteInfo route) {
+        private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
             if (mSelectedRoute != route) {
                 if (mSelectedRoute != null) {
                     if (DEBUG) {
-                        Log.d(TAG, "Route unselected: " + mSelectedRoute);
+                        Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
+                                + unselectReason);
                     }
                     mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
                     if (mSelectedRouteController != null) {
-                        mSelectedRouteController.onUnselect();
+                        mSelectedRouteController.onUnselect(unselectReason);
                         mSelectedRouteController.onRelease();
                         mSelectedRouteController = null;
                     }
@@ -2071,6 +2195,38 @@
             }
         }
 
+        public void setMediaSessionCompat(final MediaSessionCompat session) {
+            mCompatSession = session;
+            if (session == null) {
+                if (mRccMediaSession != null) {
+                    removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+                }
+            }
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                setMediaSession(session.getMediaSession());
+            } else if (android.os.Build.VERSION.SDK_INT >= 14) {
+                if (mRccMediaSession != null) {
+                    removeRemoteControlClient(mRccMediaSession.getRemoteControlClient());
+                    mRccMediaSession.removeOnActiveChangeListener(mSessionActiveListener);
+                }
+                mRccMediaSession = session;
+                session.addOnActiveChangeListener(mSessionActiveListener);
+                if (session.isActive()) {
+                    addRemoteControlClient(session.getRemoteControlClient());
+                }
+            }
+        }
+
+        public MediaSessionCompat.Token getMediaSessionToken() {
+            if (mMediaSession != null) {
+                return mMediaSession.getToken();
+            } else if (mCompatSession != null) {
+                return mCompatSession.getSessionToken();
+            }
+            return null;
+        }
+
         private int findRemoteControlClientRecord(Object rcc) {
             final int count = mRemoteControlClients.size();
             for (int i = 0; i < count; i++) {
@@ -2096,13 +2252,22 @@
                     record.updatePlaybackInfo();
                 }
                 if (mMediaSession != null) {
-                    int controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
-                    if (mPlaybackInfo.volumeHandling
-                            == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
-                        controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+                    if (mSelectedRoute == getDefaultRoute()) {
+                        // Local route
+                        mMediaSession.clearVolumeHandling();
+                    } else {
+                        int controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
+                        if (mPlaybackInfo.volumeHandling
+                                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+                            controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+                        }
+                        mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
+                                mPlaybackInfo.volume);
                     }
-                    mMediaSession.configureVolume(controlType, mPlaybackInfo.volumeMax,
-                            mPlaybackInfo.volume);
+                }
+            } else {
+                if (mMediaSession != null) {
+                    mMediaSession.clearVolumeHandling();
                 }
             }
         }
@@ -2123,7 +2288,7 @@
             private VolumeProviderCompat mVpCompat;
 
             public MediaSessionRecord(Object mediaSession) {
-                mMsCompat = MediaSessionCompat.obtain(mediaSession);
+                mMsCompat = MediaSessionCompat.obtain(mApplicationContext, mediaSession);
             }
 
             public void configureVolume(int controlType, int max, int current) {
@@ -2135,17 +2300,27 @@
                     // Otherwise create a new provider and update
                     mVpCompat = new VolumeProviderCompat(controlType, max, current) {
                         @Override
-                        public void onSetVolumeTo(int volume) {
-                            if (mSelectedRoute != null) {
-                                mSelectedRoute.requestSetVolume(volume);
-                            }
+                        public void onSetVolumeTo(final int volume) {
+                            mCallbackHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (mSelectedRoute != null) {
+                                        mSelectedRoute.requestSetVolume(volume);
+                                    }
+                                }
+                            });
                         }
 
                         @Override
-                        public void onAdjustVolume(int direction) {
-                            if (mSelectedRoute != null) {
-                                mSelectedRoute.requestUpdateVolume(direction);
-                            }
+                        public void onAdjustVolume(final int direction) {
+                            mCallbackHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (mSelectedRoute != null) {
+                                        mSelectedRoute.requestUpdateVolume(direction);
+                                    }
+                                }
+                            });
                         }
                     };
                     mMsCompat.setPlaybackToRemote(mVpCompat);
@@ -2157,6 +2332,10 @@
                 mVpCompat = null;
             }
 
+            public MediaSessionCompat.Token getToken() {
+                return mMsCompat.getSessionToken();
+            }
+
         }
 
         private final class RemoteControlClientRecord
diff --git a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
index 641e80d..e84276d 100644
--- a/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/RegisteredMediaRouteProvider.java
@@ -344,9 +344,14 @@
 
         @Override
         public void onUnselect() {
+            onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
+        }
+
+        @Override
+        public void onUnselect(int reason) {
             mSelected = false;
             if (mConnection != null) {
-                mConnection.unselectRoute(mControllerId);
+                mConnection.unselectRoute(mControllerId, reason);
             }
         }
 
@@ -525,9 +530,11 @@
                     mNextRequestId++, controllerId, null, null);
         }
 
-        public void unselectRoute(int controllerId) {
+        public void unselectRoute(int controllerId, int reason) {
+            Bundle extras = new Bundle();
+            extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
             sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
-                    mNextRequestId++, controllerId, null, null);
+                    mNextRequestId++, controllerId, null, extras);
         }
 
         public void setVolume(int controllerId, int volume) {
diff --git a/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
index 8fbd87c..1756ce8 100644
--- a/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
+++ b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.support.v4.graphics.ColorUtils;
 import android.support.v7.graphics.Palette.Swatch;
 import android.util.SparseIntArray;
 
@@ -400,7 +401,7 @@
     }
 
     private boolean shouldIgnoreColor(int color) {
-        ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
+        ColorUtils.colorToHSL(color, mTempHsl);
         return shouldIgnoreColor(mTempHsl);
     }
 
diff --git a/v7/palette/src/android/support/v7/graphics/DefaultGenerator.java b/v7/palette/src/android/support/v7/graphics/DefaultGenerator.java
new file mode 100644
index 0000000..3ee2bfa
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/DefaultGenerator.java
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+package android.support.v7.graphics;
+
+import android.support.v4.graphics.ColorUtils;
+import android.support.v7.graphics.Palette.Swatch;
+
+import java.util.List;
+
+class DefaultGenerator extends Palette.Generator {
+
+    private static final float TARGET_DARK_LUMA = 0.26f;
+    private static final float MAX_DARK_LUMA = 0.45f;
+
+    private static final float MIN_LIGHT_LUMA = 0.55f;
+    private static final float TARGET_LIGHT_LUMA = 0.74f;
+
+    private static final float MIN_NORMAL_LUMA = 0.3f;
+    private static final float TARGET_NORMAL_LUMA = 0.5f;
+    private static final float MAX_NORMAL_LUMA = 0.7f;
+
+    private static final float TARGET_MUTED_SATURATION = 0.3f;
+    private static final float MAX_MUTED_SATURATION = 0.4f;
+
+    private static final float TARGET_VIBRANT_SATURATION = 1f;
+    private static final float MIN_VIBRANT_SATURATION = 0.35f;
+
+    private static final float WEIGHT_SATURATION = 3f;
+    private static final float WEIGHT_LUMA = 6f;
+    private static final float WEIGHT_POPULATION = 1f;
+
+    private List<Swatch> mSwatches;
+
+    private int mHighestPopulation;
+
+    private Swatch mVibrantSwatch;
+    private Swatch mMutedSwatch;
+    private Swatch mDarkVibrantSwatch;
+    private Swatch mDarkMutedSwatch;
+    private Swatch mLightVibrantSwatch;
+    private Swatch mLightMutedSwatch;
+
+    @Override
+    public void generate(final List<Swatch> swatches) {
+        mSwatches = swatches;
+
+        mHighestPopulation = findMaxPopulation();
+
+        generateVariationColors();
+
+        // Now try and generate any missing colors
+        generateEmptySwatches();
+    }
+
+    @Override
+    public Swatch getVibrantSwatch() {
+        return mVibrantSwatch;
+    }
+
+    @Override
+    public Swatch getLightVibrantSwatch() {
+        return mLightVibrantSwatch;
+    }
+
+    @Override
+    public Swatch getDarkVibrantSwatch() {
+        return mDarkVibrantSwatch;
+    }
+
+    @Override
+    public Swatch getMutedSwatch() {
+        return mMutedSwatch;
+    }
+
+    @Override
+    public Swatch getLightMutedSwatch() {
+        return mLightMutedSwatch;
+    }
+
+    @Override
+    public Swatch getDarkMutedSwatch() {
+        return mDarkMutedSwatch;
+    }
+
+    private void generateVariationColors() {
+        mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+    }
+
+    /**
+     * Try and generate any missing swatches from the swatches we did find.
+     */
+    private void generateEmptySwatches() {
+        if (mVibrantSwatch == null) {
+            // If we do not have a vibrant color...
+            if (mDarkVibrantSwatch != null) {
+                // ...but we do have a dark vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
+                newHsl[2] = TARGET_NORMAL_LUMA;
+                mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
+            }
+        }
+
+        if (mDarkVibrantSwatch == null) {
+            // If we do not have a dark vibrant color...
+            if (mVibrantSwatch != null) {
+                // ...but we do have a vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mVibrantSwatch);
+                newHsl[2] = TARGET_DARK_LUMA;
+                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
+            }
+        }
+    }
+
+    /**
+     * Find the {@link Palette.Swatch} with the highest population value and return the population.
+     */
+    private int findMaxPopulation() {
+        int population = 0;
+        for (Swatch swatch : mSwatches) {
+            population = Math.max(population, swatch.getPopulation());
+        }
+        return population;
+    }
+
+    private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma,
+            float targetSaturation, float minSaturation, float maxSaturation) {
+        Swatch max = null;
+        float maxValue = 0f;
+
+        for (Swatch swatch : mSwatches) {
+            final float sat = swatch.getHsl()[1];
+            final float luma = swatch.getHsl()[2];
+
+            if (sat >= minSaturation && sat <= maxSaturation &&
+                    luma >= minLuma && luma <= maxLuma &&
+                    !isAlreadySelected(swatch)) {
+                float value = createComparisonValue(sat, targetSaturation, luma, targetLuma,
+                        swatch.getPopulation(), mHighestPopulation);
+                if (max == null || value > maxValue) {
+                    max = swatch;
+                    maxValue = value;
+                }
+            }
+        }
+
+        return max;
+    }
+
+    /**
+     * @return true if we have already selected {@code swatch}
+     */
+    private boolean isAlreadySelected(Swatch swatch) {
+        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
+                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
+                mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch;
+    }
+
+    private static float createComparisonValue(float saturation, float targetSaturation,
+            float luma, float targetLuma,
+            int population, int maxPopulation) {
+        return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION,
+                luma, targetLuma, WEIGHT_LUMA,
+                population, maxPopulation, WEIGHT_POPULATION);
+    }
+
+    private static float createComparisonValue(
+            float saturation, float targetSaturation, float saturationWeight,
+            float luma, float targetLuma, float lumaWeight,
+            int population, int maxPopulation, float populationWeight) {
+        return weightedMean(
+                invertDiff(saturation, targetSaturation), saturationWeight,
+                invertDiff(luma, targetLuma), lumaWeight,
+                population / (float) maxPopulation, populationWeight
+        );
+    }
+
+    /**
+     * Copy a {@link Swatch}'s HSL values into a new float[].
+     */
+    private static float[] copyHslValues(Swatch color) {
+        final float[] newHsl = new float[3];
+        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+        return newHsl;
+    }
+
+    /**
+     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
+     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
+     * {@code targetValue} increases.
+     *
+     * @param value the item's value
+     * @param targetValue the value which we desire
+     */
+    private static float invertDiff(float value, float targetValue) {
+        return 1f - Math.abs(value - targetValue);
+    }
+
+    private static float weightedMean(float... values) {
+        float sum = 0f;
+        float sumWeight = 0f;
+
+        for (int i = 0; i < values.length; i += 2) {
+            float value = values[i];
+            float weight = values[i + 1];
+
+            sum += (value * weight);
+            sumWeight += weight;
+        }
+
+        return sum / sumWeight;
+    }
+}
diff --git a/v7/palette/src/android/support/v7/graphics/Palette.java b/v7/palette/src/android/support/v7/graphics/Palette.java
index 0831b75..5b83d1a 100644
--- a/v7/palette/src/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/android/support/v7/graphics/Palette.java
@@ -19,7 +19,9 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.AsyncTask;
+import android.support.v4.graphics.ColorUtils;
 import android.support.v4.os.AsyncTaskCompat;
+import android.util.TimingLogger;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -40,18 +42,21 @@
  * These can be retrieved from the appropriate getter method.
  *
  * <p>
- * Instances can be created with the synchronous factory methods {@link #generate(Bitmap)} and
- * {@link #generate(Bitmap, int)}.
+ * Instances are created with a {@link Builder} which supports several options to tweak the
+ * generated Palette. See that class' documentation for more information.
  * <p>
- * These should be called on a background thread, ideally the one in
- * which you load your images on. Sometimes that is not possible, so asynchronous factory methods
- * have also been provided: {@link #generateAsync(Bitmap, PaletteAsyncListener)} and
- * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}. These can be used as so:
+ * Generation should always be completed on a background thread, ideally the one in
+ * which you load your image on. {@link Builder} supports both synchronous and asynchronous
+ * generation:
  *
  * <pre>
- * Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
- *     public void onGenerated(Palette palette) {
- *         // Do something with colors...
+ * // Synchronous
+ * Palette p = Palette.from(bitmap).generate();
+ *
+ * // Asynchronous
+ * Palette.from(bitmap).generate(new PaletteAsyncListener() {
+ *     public void onGenerated(Palette p) {
+ *         // Use generated instance
  *     }
  * });
  * </pre>
@@ -70,146 +75,71 @@
         void onGenerated(Palette palette);
     }
 
-    private static final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
+    private static final int DEFAULT_RESIZE_BITMAP_MAX_DIMENSION = 192;
     private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
 
-    private static final float TARGET_DARK_LUMA = 0.26f;
-    private static final float MAX_DARK_LUMA = 0.45f;
-
-    private static final float MIN_LIGHT_LUMA = 0.55f;
-    private static final float TARGET_LIGHT_LUMA = 0.74f;
-
-    private static final float MIN_NORMAL_LUMA = 0.3f;
-    private static final float TARGET_NORMAL_LUMA = 0.5f;
-    private static final float MAX_NORMAL_LUMA = 0.7f;
-
-    private static final float TARGET_MUTED_SATURATION = 0.3f;
-    private static final float MAX_MUTED_SATURATION = 0.4f;
-
-    private static final float TARGET_VIBRANT_SATURATION = 1f;
-    private static final float MIN_VIBRANT_SATURATION = 0.35f;
-
-    private static final float WEIGHT_SATURATION = 3f;
-    private static final float WEIGHT_LUMA = 6f;
-    private static final float WEIGHT_POPULATION = 1f;
-
     private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
     private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
 
-    private final List<Swatch> mSwatches;
-    private final int mHighestPopulation;
-
-    private Swatch mVibrantSwatch;
-    private Swatch mMutedSwatch;
-
-    private Swatch mDarkVibrantSwatch;
-    private Swatch mDarkMutedSwatch;
-
-    private Swatch mLightVibrantSwatch;
-    private Swatch mLightMutedColor;
+    private static final String LOG_TAG = "Palette";
+    private static final boolean LOG_TIMINGS = false;
 
     /**
-     * Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
+     * Start generating a {@link Palette} with the returned {@link Builder} instance.
      */
+    public static Builder from(Bitmap bitmap) {
+        return new Builder(bitmap);
+    }
+
+    /**
+     * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
+     * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
+     * list of swatches. Will return null if the {@code swatches} is null.
+     */
+    public static Palette from(List<Swatch> swatches) {
+        return new Builder(swatches).generate();
+    }
+
+    /**
+     * @deprecated Use {@link Builder} to generate the Palette.
+     */
+    @Deprecated
     public static Palette generate(Bitmap bitmap) {
-        return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
+        return from(bitmap).generate();
     }
 
     /**
-     * Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
-     * Good values for {@code numColors} depend on the source image type.
-     * For landscapes, a good values are in the range 12-16. For images which are largely made up
-     * of people's faces then this value should be increased to 24-32.
-     *
-     * @param numColors The maximum number of colors in the generated palette. Increasing this
-     *                  number will increase the time needed to compute the values.
+     * @deprecated Use {@link Builder} to generate the Palette.
      */
+    @Deprecated
     public static Palette generate(Bitmap bitmap, int numColors) {
-        checkBitmapParam(bitmap);
-        checkNumberColorsParam(numColors);
-
-        // First we'll scale down the bitmap so it's shortest dimension is 100px
-        final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
-
-        // Now generate a quantizer from the Bitmap
-        ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
-
-        // If created a new bitmap, recycle it
-        if (scaledBitmap != bitmap) {
-            scaledBitmap.recycle();
-        }
-
-        // Now return a ColorExtractor instance
-        return new Palette(quantizer.getQuantizedColors());
+        return from(bitmap).maximumColorCount(numColors).generate();
     }
 
     /**
-     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
-     * will be called with the created instance. The resulting {@link Palette} is the same as
-     * what would be created by calling {@link #generate(Bitmap)}.
-     *
-     * @param listener Listener to be invoked when the {@link Palette} has been generated.
-     *
-     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     * @deprecated Use {@link Builder} to generate the Palette.
      */
+    @Deprecated
     public static AsyncTask<Bitmap, Void, Palette> generateAsync(
             Bitmap bitmap, PaletteAsyncListener listener) {
-        return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
+        return from(bitmap).generate(listener);
     }
 
     /**
-     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
-     * will be called with the created instance. The resulting {@link Palette} is the same as what
-     * would be created by calling {@link #generate(Bitmap, int)}.
-     *
-     * @param listener Listener to be invoked when the {@link Palette} has been generated.
-     *
-     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     * @deprecated Use {@link Builder} to generate the Palette.
      */
+    @Deprecated
     public static AsyncTask<Bitmap, Void, Palette> generateAsync(
             final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
-        checkBitmapParam(bitmap);
-        checkNumberColorsParam(numColors);
-        checkAsyncListenerParam(listener);
-
-        return AsyncTaskCompat.executeParallel(
-                new AsyncTask<Bitmap, Void, Palette>() {
-                    @Override
-                    protected Palette doInBackground(Bitmap... params) {
-                        return generate(params[0], numColors);
-                    }
-
-                    @Override
-                    protected void onPostExecute(Palette colorExtractor) {
-                        listener.onGenerated(colorExtractor);
-                    }
-                }, bitmap);
+        return from(bitmap).maximumColorCount(numColors).generate(listener);
     }
 
-    private Palette(List<Swatch> swatches) {
+    private final List<Swatch> mSwatches;
+    private final Generator mGenerator;
+
+    private Palette(List<Swatch> swatches, Generator generator) {
         mSwatches = swatches;
-        mHighestPopulation = findMaxPopulation();
-
-        mVibrantSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mLightVibrantSwatch = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mDarkVibrantSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mMutedSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-
-        mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-
-        mDarkMutedSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-
-        // Now try and generate any missing colors
-        generateEmptySwatches();
+        mGenerator = generator;
     }
 
     /**
@@ -223,42 +153,42 @@
      * Returns the most vibrant swatch in the palette. Might be null.
      */
     public Swatch getVibrantSwatch() {
-        return mVibrantSwatch;
+        return mGenerator.getVibrantSwatch();
     }
 
     /**
      * Returns a light and vibrant swatch from the palette. Might be null.
      */
     public Swatch getLightVibrantSwatch() {
-        return mLightVibrantSwatch;
+        return mGenerator.getLightVibrantSwatch();
     }
 
     /**
      * Returns a dark and vibrant swatch from the palette. Might be null.
      */
     public Swatch getDarkVibrantSwatch() {
-        return mDarkVibrantSwatch;
+        return mGenerator.getDarkVibrantSwatch();
     }
 
     /**
      * Returns a muted swatch from the palette. Might be null.
      */
     public Swatch getMutedSwatch() {
-        return mMutedSwatch;
+        return mGenerator.getMutedSwatch();
     }
 
     /**
      * Returns a muted and light swatch from the palette. Might be null.
      */
     public Swatch getLightMutedSwatch() {
-        return mLightMutedColor;
+        return mGenerator.getLightMutedSwatch();
     }
 
     /**
      * Returns a muted and dark swatch from the palette. Might be null.
      */
     public Swatch getDarkMutedSwatch() {
-        return mDarkMutedSwatch;
+        return mGenerator.getDarkMutedSwatch();
     }
 
     /**
@@ -267,7 +197,8 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getVibrantColor(int defaultColor) {
-        return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
+        Swatch swatch = getVibrantSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
@@ -276,7 +207,8 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getLightVibrantColor(int defaultColor) {
-        return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
+        Swatch swatch = getLightVibrantSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
@@ -285,7 +217,8 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getDarkVibrantColor(int defaultColor) {
-        return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
+        Swatch swatch = getDarkVibrantSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
@@ -294,7 +227,8 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getMutedColor(int defaultColor) {
-        return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
+        Swatch swatch = getMutedSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
@@ -303,7 +237,8 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getLightMutedColor(int defaultColor) {
-        return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
+        Swatch swatch = getLightMutedSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
@@ -312,219 +247,29 @@
      * @param defaultColor value to return if the swatch isn't available
      */
     public int getDarkMutedColor(int defaultColor) {
-        return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
+        Swatch swatch = getDarkMutedSwatch();
+        return swatch != null ? swatch.getRgb() : defaultColor;
     }
 
     /**
-     * @return true if we have already selected {@code swatch}
+     * Scale the bitmap down so that it's largest dimension is {@code targetMaxDimension}.
+     * If {@code bitmap} is smaller than this, then it is returned.
      */
-    private boolean isAlreadySelected(Swatch swatch) {
-        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
-                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
-                mDarkMutedSwatch == swatch || mLightMutedColor == swatch;
-    }
+    private static Bitmap scaleBitmapDown(Bitmap bitmap, final int targetMaxDimension) {
+        final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
 
-    private Swatch findColor(float targetLuma, float minLuma, float maxLuma,
-                             float targetSaturation, float minSaturation, float maxSaturation) {
-        Swatch max = null;
-        float maxValue = 0f;
-
-        for (Swatch swatch : mSwatches) {
-            final float sat = swatch.getHsl()[1];
-            final float luma = swatch.getHsl()[2];
-
-            if (sat >= minSaturation && sat <= maxSaturation &&
-                    luma >= minLuma && luma <= maxLuma &&
-                    !isAlreadySelected(swatch)) {
-                float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
-                        swatch.getPopulation(), mHighestPopulation);
-                if (max == null || thisValue > maxValue) {
-                    max = swatch;
-                    maxValue = thisValue;
-                }
-            }
-        }
-
-        return max;
-    }
-
-    /**
-     * Try and generate any missing swatches from the swatches we did find.
-     */
-    private void generateEmptySwatches() {
-        if (mVibrantSwatch == null) {
-            // If we do not have a vibrant color...
-            if (mDarkVibrantSwatch != null) {
-                // ...but we do have a dark vibrant, generate the value by modifying the luma
-                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
-                newHsl[2] = TARGET_NORMAL_LUMA;
-                mVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
-            }
-        }
-
-        if (mDarkVibrantSwatch == null) {
-            // If we do not have a dark vibrant color...
-            if (mVibrantSwatch != null) {
-                // ...but we do have a vibrant, generate the value by modifying the luma
-                final float[] newHsl = copyHslValues(mVibrantSwatch);
-                newHsl[2] = TARGET_DARK_LUMA;
-                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
-            }
-        }
-    }
-
-    /**
-     * Find the {@link Swatch} with the highest population value and return the population.
-     */
-    private int findMaxPopulation() {
-        int population = 0;
-        for (Swatch swatch : mSwatches) {
-            population = Math.max(population, swatch.getPopulation());
-        }
-        return population;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        Palette palette = (Palette) o;
-
-        if (mSwatches != null ? !mSwatches.equals(palette.mSwatches) : palette.mSwatches != null) {
-            return false;
-        }
-        if (mDarkMutedSwatch != null ? !mDarkMutedSwatch.equals(palette.mDarkMutedSwatch)
-                : palette.mDarkMutedSwatch != null) {
-            return false;
-        }
-        if (mDarkVibrantSwatch != null ? !mDarkVibrantSwatch.equals(palette.mDarkVibrantSwatch)
-                : palette.mDarkVibrantSwatch != null) {
-            return false;
-        }
-        if (mLightMutedColor != null ? !mLightMutedColor.equals(palette.mLightMutedColor)
-                : palette.mLightMutedColor != null) {
-            return false;
-        }
-        if (mLightVibrantSwatch != null ? !mLightVibrantSwatch.equals(palette.mLightVibrantSwatch)
-                : palette.mLightVibrantSwatch != null) {
-            return false;
-        }
-        if (mMutedSwatch != null ? !mMutedSwatch.equals(palette.mMutedSwatch)
-                : palette.mMutedSwatch != null) {
-            return false;
-        }
-        if (mVibrantSwatch != null ? !mVibrantSwatch.equals(palette.mVibrantSwatch)
-                : palette.mVibrantSwatch != null) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mSwatches != null ? mSwatches.hashCode() : 0;
-        result = 31 * result + (mVibrantSwatch != null ? mVibrantSwatch.hashCode() : 0);
-        result = 31 * result + (mMutedSwatch != null ? mMutedSwatch.hashCode() : 0);
-        result = 31 * result + (mDarkVibrantSwatch != null ? mDarkVibrantSwatch.hashCode() : 0);
-        result = 31 * result + (mDarkMutedSwatch != null ? mDarkMutedSwatch.hashCode() : 0);
-        result = 31 * result + (mLightVibrantSwatch != null ? mLightVibrantSwatch.hashCode() : 0);
-        result = 31 * result + (mLightMutedColor != null ? mLightMutedColor.hashCode() : 0);
-        return result;
-    }
-
-    /**
-     * Scale the bitmap down so that it's smallest dimension is
-     * {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
-     * is returned.
-     */
-    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
-        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
-
-        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
+        if (maxDimension <= targetMaxDimension) {
             // If the bitmap is small enough already, just return it
             return bitmap;
         }
 
-        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
+        final float scaleRatio = targetMaxDimension / (float) maxDimension;
         return Bitmap.createScaledBitmap(bitmap,
                 Math.round(bitmap.getWidth() * scaleRatio),
                 Math.round(bitmap.getHeight() * scaleRatio),
                 false);
     }
 
-    private static float createComparisonValue(float saturation, float targetSaturation,
-            float luma, float targetLuma,
-            int population, int highestPopulation) {
-        return weightedMean(
-                invertDiff(saturation, targetSaturation), WEIGHT_SATURATION,
-                invertDiff(luma, targetLuma), WEIGHT_LUMA,
-                population / (float) highestPopulation, WEIGHT_POPULATION
-        );
-    }
-
-    /**
-     * Copy a {@link Swatch}'s HSL values into a new float[].
-     */
-    private static float[] copyHslValues(Swatch color) {
-        final float[] newHsl = new float[3];
-        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
-        return newHsl;
-    }
-
-    /**
-     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
-     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
-     * {@code targetValue} increases.
-     *
-     * @param value the item's value
-     * @param targetValue the value which we desire
-     */
-    private static float invertDiff(float value, float targetValue) {
-        return 1f - Math.abs(value - targetValue);
-    }
-
-    private static float weightedMean(float... values) {
-        float sum = 0f;
-        float sumWeight = 0f;
-
-        for (int i = 0; i < values.length; i += 2) {
-            float value = values[i];
-            float weight = values[i + 1];
-
-            sum += (value * weight);
-            sumWeight += weight;
-        }
-
-        return sum / sumWeight;
-    }
-
-    private static void checkBitmapParam(Bitmap bitmap) {
-        if (bitmap == null) {
-            throw new IllegalArgumentException("bitmap can not be null");
-        }
-        if (bitmap.isRecycled()) {
-            throw new IllegalArgumentException("bitmap can not be recycled");
-        }
-    }
-
-    private static void checkNumberColorsParam(int numColors) {
-        if (numColors < 1) {
-            throw new IllegalArgumentException("numColors must be 1 of greater");
-        }
-    }
-
-    private static void checkAsyncListenerParam(PaletteAsyncListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener can not be null");
-        }
-    }
-
     /**
      * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
      * by calling {@link #getRgb()}.
@@ -540,11 +285,11 @@
 
         private float[] mHsl;
 
-        Swatch(int rgbColor, int population) {
-            mRed = Color.red(rgbColor);
-            mGreen = Color.green(rgbColor);
-            mBlue = Color.blue(rgbColor);
-            mRgb = rgbColor;
+        public Swatch(int color, int population) {
+            mRed = Color.red(color);
+            mGreen = Color.green(color);
+            mBlue = Color.blue(color);
+            mRgb = color;
             mPopulation = population;
         }
 
@@ -571,9 +316,8 @@
          */
         public float[] getHsl() {
             if (mHsl == null) {
-                // Lazily generate HSL values from RGB
                 mHsl = new float[3];
-                ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
+                ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
             }
             return mHsl;
         }
@@ -605,10 +349,41 @@
 
         private void ensureTextColorsGenerated() {
             if (!mGeneratedTextColors) {
-                mTitleTextColor = ColorUtils.getTextColorForBackground(mRgb,
-                        MIN_CONTRAST_TITLE_TEXT);
-                mBodyTextColor = ColorUtils.getTextColorForBackground(mRgb,
-                        MIN_CONTRAST_BODY_TEXT);
+                // First check white, as most colors will be dark
+                final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
+                final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+                if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
+                    // If we found valid light values, use them and return
+                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
+                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
+                    mGeneratedTextColors = true;
+                    return;
+                }
+
+                final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
+                final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
+                        Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);
+
+                if (darkBodyAlpha != -1 && darkBodyAlpha != -1) {
+                    // If we found valid dark values, use them and return
+                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
+                    mGeneratedTextColors = true;
+                    return;
+                }
+
+                // If we reach here then we can not find title and body values which use the same
+                // lightness, we need to use mismatched values
+                mBodyTextColor = lightBodyAlpha != -1
+                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha)
+                        : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
+                mTitleTextColor = lightTitleAlpha != -1
+                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
+                        : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
                 mGeneratedTextColors = true;
             }
         }
@@ -619,9 +394,10 @@
                     .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
                     .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
                     .append(" [Population: ").append(mPopulation).append(']')
-                    .append(" [Title Text: #").append(Integer.toHexString(mTitleTextColor)).append(']')
-                    .append(" [Body Text: #").append(Integer.toHexString(mBodyTextColor)).append(']')
-                    .toString();
+                    .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
+                    .append(']')
+                    .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
+                    .append(']').toString();
         }
 
         @Override
@@ -643,4 +419,232 @@
         }
     }
 
+    /**
+     * Builder class for generating {@link Palette} instances.
+     */
+    public static final class Builder {
+        private List<Swatch> mSwatches;
+        private Bitmap mBitmap;
+        private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
+        private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION;
+
+        private Generator mGenerator;
+
+        /**
+         * Construct a new {@link Builder} using a source {@link Bitmap}
+         */
+        public Builder(Bitmap bitmap) {
+            if (bitmap == null || bitmap.isRecycled()) {
+                throw new IllegalArgumentException("Bitmap is not valid");
+            }
+            mBitmap = bitmap;
+        }
+
+        /**
+         * Construct a new {@link Builder} using a list of {@link Swatch} instances.
+         * Typically only used for testing.
+         */
+        public Builder(List<Swatch> swatches) {
+            if (swatches == null || swatches.isEmpty()) {
+                throw new IllegalArgumentException("List of Swatches is not valid");
+            }
+            mSwatches = swatches;
+        }
+
+        /**
+         * Set the {@link Generator} to use when generating the {@link Palette}. If this is called
+         * with {@code null} then the default generator will be used.
+         */
+        public Builder generator(Generator generator) {
+            mGenerator = generator;
+            return this;
+        }
+
+        /**
+         * Set the maximum number of colors to use in the quantization step when using a
+         * {@link android.graphics.Bitmap} as the source.
+         * <p>
+         * Good values for depend on the source image type. For landscapes, good values are in
+         * the range 10-16. For images which are largely made up of people's faces then this
+         * value should be increased to ~24.
+         */
+        public Builder maximumColorCount(int colors) {
+            mMaxColors = colors;
+            return this;
+        }
+
+        /**
+         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+         * If the bitmap's largest dimension is greater than the value specified, then the bitmap
+         * will be resized so that it's largest dimension matches {@code maxDimension}. If the
+         * bitmap is smaller or equal, the original is used as-is.
+         * <p>
+         * This value has a large effect on the processing time. The larger the resized image is,
+         * the greater time it will take to generate the palette. The smaller the image is, the
+         * more detail is lost in the resulting image and thus less precision for color selection.
+         */
+        public Builder resizeBitmapSize(int maxDimension) {
+            mResizeMaxDimension = maxDimension;
+            return this;
+        }
+
+        /**
+         * Generate and return the {@link Palette} synchronously.
+         */
+        public Palette generate() {
+            final TimingLogger logger = LOG_TIMINGS
+                    ? new TimingLogger(LOG_TAG, "Generation")
+                    : null;
+
+            List<Swatch> swatches;
+
+            if (mBitmap != null) {
+                // We have a Bitmap so we need to quantization to reduce the number of colors
+
+                if (mResizeMaxDimension <= 0) {
+                    throw new IllegalArgumentException(
+                            "Minimum dimension size for resizing should should be >= 1");
+                }
+
+                // First we'll scale down the bitmap so it's largest dimension is as specified
+                final Bitmap scaledBitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension);
+
+                if (logger != null) {
+                    logger.addSplit("Processed Bitmap");
+                }
+
+                // Now generate a quantizer from the Bitmap
+                ColorCutQuantizer quantizer = ColorCutQuantizer
+                        .fromBitmap(scaledBitmap, mMaxColors);
+
+                // If created a new bitmap, recycle it
+                if (scaledBitmap != mBitmap) {
+                    scaledBitmap.recycle();
+                }
+                swatches = quantizer.getQuantizedColors();
+
+                if (logger != null) {
+                    logger.addSplit("Color quantization completed");
+                }
+            } else {
+                // Else we're using the provided swatches
+                swatches = mSwatches;
+            }
+
+            // If we haven't been provided with a generator, use the default
+            if (mGenerator == null) {
+                mGenerator = new DefaultGenerator();
+            }
+
+            // Now call let the Generator do it's thing
+            mGenerator.generate(swatches);
+
+            if (logger != null) {
+                logger.addSplit("Generator.generate() completed");
+            }
+
+            // Now create a Palette instance
+            Palette p = new Palette(swatches, mGenerator);
+
+            if (logger != null) {
+                logger.addSplit("Created Palette");
+                logger.dumpToLog();
+            }
+
+            return p;
+        }
+
+        /**
+         * Generate the {@link Palette} asynchronously. The provided listener's
+         * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when
+         * generated.
+         */
+        public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) {
+            if (listener == null) {
+                throw new IllegalArgumentException("listener can not be null");
+            }
+
+            return AsyncTaskCompat.executeParallel(
+                    new AsyncTask<Bitmap, Void, Palette>() {
+                        @Override
+                        protected Palette doInBackground(Bitmap... params) {
+                            return generate();
+                        }
+
+                        @Override
+                        protected void onPostExecute(Palette colorExtractor) {
+                            listener.onGenerated(colorExtractor);
+                        }
+                    }, mBitmap);
+        }
+    }
+
+    /**
+     * Extension point for {@link Palette} which allows custom processing of the list of
+     * {@link Palette.Swatch} instances which represent an image.
+     * <p>
+     * You should do as much processing as possible during the
+     * {@link #generate(java.util.List)} method call. The other methods in this class
+     * may be called multiple times to retrieve an appropriate {@link Palette.Swatch}.
+     * <p>
+     * Usage of a custom {@link Generator} is done with {@link Builder#generator(Generator)} as so:
+     * <pre>
+     * Generator customGenerator = ...;
+     * Palette.from(bitmap).generator(customGenerator).generate();
+     * </pre>
+     */
+    public static abstract class Generator {
+
+        /**
+         * This method will be called with the {@link Palette.Swatch} that represent an image.
+         * You should process this list so that you have appropriate values when the other methods in
+         * class are called.
+         * <p>
+         * This method will probably be called on a background thread.
+         */
+        public abstract void generate(List<Palette.Swatch> swatches);
+
+        /**
+         * Return the most vibrant {@link Palette.Swatch}
+         */
+        public Palette.Swatch getVibrantSwatch() {
+            return null;
+        }
+
+        /**
+         * Return a light and vibrant {@link Palette.Swatch}
+         */
+        public Palette.Swatch getLightVibrantSwatch() {
+            return null;
+        }
+
+        /**
+         * Return a dark and vibrant {@link Palette.Swatch}
+         */
+        public Palette.Swatch getDarkVibrantSwatch() {
+            return null;
+        }
+
+        /**
+         * Return a muted {@link Palette.Swatch}
+         */
+        public Palette.Swatch getMutedSwatch() {
+            return null;
+        }
+
+        /**
+         * Return a muted and light {@link Palette.Swatch}
+         */
+        public Palette.Swatch getLightMutedSwatch() {
+            return null;
+        }
+
+        /**
+         * Return a muted and dark {@link Palette.Swatch}
+         */
+        public Palette.Swatch getDarkMutedSwatch() {
+            return null;
+        }
+    }
+
 }
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index 40a58ad..4d0b56492 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -4,6 +4,7 @@
 
 dependencies {
     compile project(':support-v4')
+    compile project(':support-annotations')
 }
 
 android {
diff --git a/v7/recyclerview/src/android/support/v7/util/SortedList.java b/v7/recyclerview/src/android/support/v7/util/SortedList.java
new file mode 100644
index 0000000..688e032
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/util/SortedList.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.util;
+
+import java.lang.reflect.Array;
+
+/**
+ * A Sorted list implementation that can keep items in order and also notify for changes in the
+ * list
+ * such that it can be bound to a {@link android.support.v7.widget.RecyclerView.Adapter
+ * RecyclerView.Adapter}.
+ * <p>
+ * It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses
+ * binary search to retrieve items. If the sorting criteria of your items may change, make sure you
+ * call appropriate methods while editing them to avoid data inconsistencies.
+ * <p>
+ * You can control the order of items and change notifications via the {@link Callback} parameter.
+ */
+@SuppressWarnings("unchecked")
+public class SortedList<T> {
+
+    /**
+     * Used by {@link #indexOf(Object)} when he item cannot be found in the list.
+     */
+    public static final int INVALID_POSITION = -1;
+
+    private static final int MIN_CAPACITY = 10;
+    private static final int CAPACITY_GROWTH = MIN_CAPACITY;
+    private static final int INSERTION = 1;
+    private static final int DELETION = 1 << 1;
+    private static final int LOOKUP = 1 << 2;
+    T[] mData;
+
+    /**
+     * The callback instance that controls the behavior of the SortedList and get notified when
+     * changes happen.
+     */
+    private Callback mCallback;
+
+    private BatchedCallback mBatchedCallback;
+
+    private int mSize;
+    private final Class<T> mTClass;
+
+    /**
+     * Creates a new SortedList of type T.
+     *
+     * @param klass    The class of the contents of the SortedList.
+     * @param callback The callback that controls the behavior of SortedList.
+     */
+    public SortedList(Class<T> klass, Callback<T> callback) {
+        this(klass, callback, MIN_CAPACITY);
+    }
+
+    /**
+     * Creates a new SortedList of type T.
+     *
+     * @param klass           The class of the contents of the SortedList.
+     * @param callback        The callback that controls the behavior of SortedList.
+     * @param initialCapacity The initial capacity to hold items.
+     */
+    public SortedList(Class<T> klass, Callback<T> callback, int initialCapacity) {
+        mTClass = klass;
+        mData = (T[]) Array.newInstance(klass, initialCapacity);
+        mCallback = callback;
+        mSize = 0;
+    }
+
+    /**
+     * The number of items in the list.
+     *
+     * @return The number of items in the list.
+     */
+    public int size() {
+        return mSize;
+    }
+
+    /**
+     * Adds the given item to the list. If this is a new item, SortedList calls
+     * {@link Callback#onInserted(int, int)}.
+     * <p>
+     * If the item already exists in the list and its sorting criteria is not changed, it is
+     * replaced with the existing Item. SortedList uses
+     * {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item
+     * and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should
+     * call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the
+     * reference to the old item and puts the new item into the backing array even if
+     * {@link Callback#areContentsTheSame(Object, Object)} returns false.
+     * <p>
+     * If the sorting criteria of the item is changed, SortedList won't be able to find
+     * its duplicate in the list which will result in having a duplicate of the Item in the list.
+     * If you need to update sorting criteria of an item that already exists in the list,
+     * use {@link #updateItemAt(int, Object)}. You can find the index of the item using
+     * {@link #indexOf(Object)} before you update the object.
+     *
+     * @param item The item to be added into the list.
+     * @return The index of the newly added item.
+     * @see {@link Callback#compare(Object, Object)}
+     * @see {@link Callback#areItemsTheSame(Object, Object)}
+     * @see {@link Callback#areContentsTheSame(Object, Object)}}
+     */
+    public int add(T item) {
+        return add(item, true);
+    }
+
+    /**
+     * Batches adapter updates that happen between calling this method until calling
+     * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
+     * and they are placed into consecutive indices, SortedList calls
+     * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
+     * cannot be merged with the previous event, the previous event is dispatched
+     * to the callback instantly.
+     * <p>
+     * After running your data updates, you <b>must</b> call {@link #endBatchedUpdates()}
+     * which will dispatch any deferred data change event to the current callback.
+     * <p>
+     * A sample implementation may look like this:
+     * <pre>
+     *     mSortedList.beginBatchedUpdates();
+     *     try {
+     *         mSortedList.add(item1)
+     *         mSortedList.add(item2)
+     *         mSortedList.remove(item3)
+     *         ...
+     *     } finally {
+     *         mSortedList.endBatchedUpdates();
+     *     }
+     * </pre>
+     * <p>
+     * Instead of using this method to batch calls, you can use a Callback that extends
+     * {@link BatchedCallback}. In that case, you must make sure that you are manually calling
+     * {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes.
+     * Failing to do so may create data inconsistencies with the Callback.
+     * <p>
+     * If the current Callback in an instance of {@link BatchedCallback}, calling this method
+     * has no effect.
+     */
+    public void beginBatchedUpdates() {
+        if (mCallback instanceof BatchedCallback) {
+            return;
+        }
+        if (mBatchedCallback == null) {
+            mBatchedCallback = new BatchedCallback(mCallback);
+        }
+        mCallback = mBatchedCallback;
+    }
+
+    /**
+     * Ends the update transaction and dispatches any remaining event to the callback.
+     */
+    public void endBatchedUpdates() {
+        if (mCallback instanceof BatchedCallback) {
+            ((BatchedCallback) mCallback).dispatchLastEvent();
+        }
+        if (mCallback == mBatchedCallback) {
+            mCallback = mBatchedCallback.mWrappedCallback;
+        }
+    }
+
+    private int add(T item, boolean notify) {
+        int index = findIndexOf(item, INSERTION);
+        if (index == INVALID_POSITION) {
+            index = 0;
+        } else if (index < mSize) {
+            T existing = mData[index];
+            if (mCallback.areItemsTheSame(existing, item)) {
+                if (mCallback.areContentsTheSame(existing, item)) {
+                    //no change but still replace the item
+                    mData[index] = item;
+                    return index;
+                } else {
+                    mData[index] = item;
+                    mCallback.onChanged(index, 1);
+                    return index;
+                }
+            }
+        }
+        addToData(index, item);
+        if (notify) {
+            mCallback.onInserted(index, 1);
+        }
+        return index;
+    }
+
+    /**
+     * Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}.
+     *
+     * @param item The item to be removed from the list.
+     * @return True if item is removed, false if item cannot be found in the list.
+     */
+    public boolean remove(T item) {
+        return remove(item, true);
+    }
+
+    /**
+     * Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}.
+     *
+     * @param index The index of the item to be removed.
+     * @return The removed item.
+     */
+    public T removeItemAt(int index) {
+        T item = get(index);
+        removeItemAtIndex(index, true);
+        return item;
+    }
+
+    private boolean remove(T item, boolean notify) {
+        int index = findIndexOf(item, DELETION);
+        if (index == INVALID_POSITION) {
+            return false;
+        }
+        removeItemAtIndex(index, notify);
+        return true;
+    }
+
+    private void removeItemAtIndex(int index, boolean notify) {
+        System.arraycopy(mData, index + 1, mData, index, mSize - index - 1);
+        mSize--;
+        mData[mSize] = null;
+        if (notify) {
+            mCallback.onRemoved(index, 1);
+        }
+    }
+
+    /**
+     * Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or
+     * {@link Callback#onMoved(int, int)} if necessary.
+     * <p>
+     * You can use this method if you need to change an existing Item such that its position in the
+     * list may change.
+     * <p>
+     * If the new object is a different object (<code>get(index) != item</code>) and
+     * {@link Callback#areContentsTheSame(Object, Object)} returns <code>true</code>, SortedList
+     * avoids calling {@link Callback#onChanged(int, int)} otherwise it calls
+     * {@link Callback#onChanged(int, int)}.
+     * <p>
+     * If the new position of the item is different than the provided <code>index</code>,
+     * SortedList
+     * calls {@link Callback#onMoved(int, int)}.
+     *
+     * @param index The index of the item to replace
+     * @param item  The item to replace the item at the given Index.
+     * @see #add(Object)
+     */
+    public void updateItemAt(int index, T item) {
+        final T existing = get(index);
+        // assume changed if the same object is given back
+        boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
+        if (existing != item) {
+            // different items, we can use comparison and may avoid lookup
+            final int cmp = mCallback.compare(existing, item);
+            if (cmp == 0) {
+                mData[index] = item;
+                if (contentsChanged) {
+                    mCallback.onChanged(index, 1);
+                }
+                return;
+            }
+        }
+        if (contentsChanged) {
+            mCallback.onChanged(index, 1);
+        }
+        // TODO this done in 1 pass to avoid shifting twice.
+        removeItemAtIndex(index, false);
+        int newIndex = add(item, false);
+        if (index != newIndex) {
+            mCallback.onMoved(index, newIndex);
+        }
+    }
+
+    /**
+     * This method can be used to recalculate the position of the item at the given index, without
+     * triggering an {@link Callback#onChanged(int, int)} callback.
+     * <p>
+     * If you are editing objects in the list such that their position in the list may change but
+     * you don't want to trigger an onChange animation, you can use this method to re-position it.
+     * If the item changes position, SortedList will call {@link Callback#onMoved(int, int)}
+     * without
+     * calling {@link Callback#onChanged(int, int)}.
+     * <p>
+     * A sample usage may look like:
+     *
+     * <pre>
+     *     final int position = mSortedList.indexOf(item);
+     *     item.incrementPriority(); // assume items are sorted by priority
+     *     mSortedList.recalculatePositionOfItemAt(position);
+     * </pre>
+     * In the example above, because the sorting criteria of the item has been changed,
+     * mSortedList.indexOf(item) will not be able to find the item. This is why the code above
+     * first
+     * gets the position before editing the item, edits it and informs the SortedList that item
+     * should be repositioned.
+     *
+     * @param index The current index of the Item whose position should be re-calculated.
+     * @see #updateItemAt(int, Object)
+     * @see #add(Object)
+     */
+    public void recalculatePositionOfItemAt(int index) {
+        // TODO can be improved
+        final T item = get(index);
+        removeItemAtIndex(index, false);
+        int newIndex = add(item, false);
+        if (index != newIndex) {
+            mCallback.onMoved(index, newIndex);
+        }
+    }
+
+    /**
+     * Returns the item at the given index.
+     *
+     * @param index The index of the item to retrieve.
+     * @return The item at the given index.
+     * @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the
+     *                                             size of the list.
+     */
+    public T get(int index) throws IndexOutOfBoundsException {
+        if (index >= mSize || index < 0) {
+            throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is "
+                    + mSize);
+        }
+        return mData[index];
+    }
+
+    /**
+     * Returns the position of the provided item.
+     *
+     * @param item The item to query for position.
+     * @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the
+     * list.
+     */
+    public int indexOf(T item) {
+        return findIndexOf(item, LOOKUP);
+    }
+
+    private int findIndexOf(T item, int reason) {
+        int left = 0;
+        int right = mSize;
+        while (left < right) {
+            final int middle = (left + right) / 2;
+            T myItem = mData[middle];
+            final int cmp = mCallback.compare(myItem, item);
+            if (cmp < 0) {
+                left = middle + 1;
+            } else if (cmp == 0) {
+                if (mCallback.areItemsTheSame(myItem, item)) {
+                    return middle;
+                } else {
+                    int exact = linearEqualitySearch(item, middle, left, right);
+                    if (reason == INSERTION) {
+                        return exact == INVALID_POSITION ? middle : exact;
+                    } else {
+                        return exact;
+                    }
+                }
+            } else {
+                right = middle;
+            }
+        }
+        return reason == INSERTION ? left : INVALID_POSITION;
+    }
+
+    private int linearEqualitySearch(T item, int middle, int left, int right) {
+        // go left
+        for (int next = middle - 1; next >= left; next--) {
+            T nextItem = mData[next];
+            int cmp = mCallback.compare(nextItem, item);
+            if (cmp != 0) {
+                break;
+            }
+            if (mCallback.areItemsTheSame(nextItem, item)) {
+                return next;
+            }
+        }
+        for (int next = middle + 1; next < right; next++) {
+            T nextItem = mData[next];
+            int cmp = mCallback.compare(nextItem, item);
+            if (cmp != 0) {
+                break;
+            }
+            if (mCallback.areItemsTheSame(nextItem, item)) {
+                return next;
+            }
+        }
+        return INVALID_POSITION;
+    }
+
+    private void addToData(int index, T item) {
+        if (index > mSize) {
+            throw new IndexOutOfBoundsException(
+                    "cannot add item to " + index + " because size is " + mSize);
+        }
+        if (mSize == mData.length) {
+            // we are at the limit enlarge
+            T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH);
+            System.arraycopy(mData, 0, newData, 0, index);
+            newData[index] = item;
+            System.arraycopy(mData, index, newData, index + 1, mSize - index);
+            mData = newData;
+        } else {
+            // just shift, we fit
+            System.arraycopy(mData, index, mData, index + 1, mSize - index);
+            mData[index] = item;
+        }
+        mSize++;
+    }
+
+    /**
+     * The class that controls the behavior of the {@link SortedList}.
+     * <p>
+     * It defines how items should be sorted and how duplicates should be handled.
+     * <p>
+     * SortedList calls the callback methods on this class to notify changes about the underlying
+     * data.
+     */
+    public static abstract class Callback<T2> {
+
+        /**
+         * Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and
+         * return how they should be ordered.
+         *
+         * @param o1 The first object to compare.
+         * @param o2 The second object to compare.
+         * @return a negative integer, zero, or a positive integer as the
+         * first argument is less than, equal to, or greater than the
+         * second.
+         */
+        abstract public int compare(T2 o1, T2 o2);
+
+        /**
+         * Called by the SortedList when an item is inserted at the given position.
+         *
+         * @param position The position of the new item.
+         * @param count    The number of items that have been added.
+         */
+        abstract public void onInserted(int position, int count);
+
+        /**
+         * Called by the SortedList when an item is removed from the given position.
+         *
+         * @param position The position of the item which has been removed.
+         * @param count    The number of items which have been removed.
+         */
+        abstract public void onRemoved(int position, int count);
+
+        /**
+         * Called by the SortedList when an item changes its position in the list.
+         *
+         * @param fromPosition The previous position of the item before the move.
+         * @param toPosition   The new position of the item.
+         */
+        abstract public void onMoved(int fromPosition, int toPosition);
+
+        /**
+         * Called by the SortedList when the item at the given position is updated.
+         *
+         * @param position The position of the item which has been updated.
+         * @param count    The number of items which has changed.
+         */
+        abstract public void onChanged(int position, int count);
+
+        /**
+         * Called by the SortedList when it wants to check whether two items have the same data
+         * or not. SortedList uses this information to decide whether it should call
+         * {@link #onChanged(int, int)} or not.
+         * <p>
+         * SortedList uses this method to check equality instead of {@link Object#equals(Object)}
+         * so
+         * that you can change its behavior depending on your UI.
+         * <p>
+         * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
+         * RecyclerView.Adapter}, you should
+         * return whether the items' visual representations are the same or not.
+         *
+         * @param oldItem The previous representation of the object.
+         * @param newItem The new object that replaces the previous one.
+         * @return True if the contents of the items are the same or false if they are different.
+         */
+        abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
+
+        /**
+         * Called by the SortedList to decide whether two object represent the same Item or not.
+         * <p>
+         * For example, if your items have unique ids, this method should check their equality.
+         *
+         * @param item1 The first item to check.
+         * @param item2 The second item to check.
+         * @return True if the two items represent the same object or false if they are different.
+         */
+        abstract public boolean areItemsTheSame(T2 item1, T2 item2);
+    }
+
+    /**
+     * A callback implementation that can batch notify events dispatched by the SortedList.
+     * <p>
+     * This class can be useful if you want to do multiple operations on a SortedList but don't
+     * want to dispatch each event one by one, which may result in a performance issue.
+     * <p>
+     * For example, if you are going to add multiple items to a SortedList, BatchedCallback call
+     * convert individual <code>onInserted(index, 1)</code> calls into one
+     * <code>onInserted(index, N)</code> if items are added into consecutive indices. This change
+     * can help RecyclerView resolve changes much more easily.
+     * <p>
+     * If consecutive changes in the SortedList are not suitable for batching, BatchingCallback
+     * dispatches them as soon as such case is detected. After your edits on the SortedList is
+     * complete, you <b>must</b> always call {@link BatchedCallback#dispatchLastEvent()} to flush
+     * all changes to the Callback.
+     */
+    public static class BatchedCallback<T2> extends Callback<T2> {
+
+        private final Callback<T2> mWrappedCallback;
+        static final int TYPE_NONE = 0;
+        static final int TYPE_ADD = 1;
+        static final int TYPE_REMOVE = 2;
+        static final int TYPE_CHANGE = 3;
+        static final int TYPE_MOVE = 4;
+
+        int mLastEventType = TYPE_NONE;
+        int mLastEventPosition = -1;
+        int mLastEventCount = -1;
+
+        /**
+         * Creates a new BatchedCallback that wraps the provided Callback.
+         *
+         * @param wrappedCallback The Callback which should received the data change callbacks.
+         *                        Other method calls (e.g. {@link #compare(Object, Object)} from
+         *                        the SortedList are directly forwarded to this Callback.
+         */
+        public BatchedCallback(Callback<T2> wrappedCallback) {
+            mWrappedCallback = wrappedCallback;
+        }
+
+        @Override
+        public int compare(T2 o1, T2 o2) {
+            return mWrappedCallback.compare(o1, o2);
+        }
+
+        @Override
+        public void onInserted(int position, int count) {
+            if (mLastEventType == TYPE_ADD && position >= mLastEventPosition
+                    && position <= mLastEventPosition + mLastEventCount) {
+                mLastEventCount += count;
+                mLastEventPosition = Math.min(position, mLastEventPosition);
+                return;
+            }
+            dispatchLastEvent();
+            mLastEventPosition = position;
+            mLastEventCount = count;
+            mLastEventType = TYPE_ADD;
+        }
+
+        @Override
+        public void onRemoved(int position, int count) {
+            if (mLastEventType == TYPE_REMOVE && mLastEventPosition == position) {
+                mLastEventCount += count;
+                return;
+            }
+            dispatchLastEvent();
+            mLastEventPosition = position;
+            mLastEventCount = count;
+            mLastEventType = TYPE_REMOVE;
+        }
+
+        @Override
+        public void onMoved(int fromPosition, int toPosition) {
+            dispatchLastEvent();//moves are not merged
+            mWrappedCallback.onMoved(fromPosition, toPosition);
+        }
+
+        @Override
+        public void onChanged(int position, int count) {
+            if (mLastEventType == TYPE_CHANGE &&
+                    !(position > mLastEventPosition + mLastEventCount
+                            || position + count < mLastEventPosition)) {
+                // take potential overlap into account
+                int previousEnd = mLastEventPosition + mLastEventCount;
+                mLastEventPosition = Math.min(position, mLastEventPosition);
+                mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition;
+                return;
+            }
+            dispatchLastEvent();
+            mLastEventPosition = position;
+            mLastEventCount = count;
+            mLastEventType = TYPE_CHANGE;
+        }
+
+        @Override
+        public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
+            return mWrappedCallback.areContentsTheSame(oldItem, newItem);
+        }
+
+        @Override
+        public boolean areItemsTheSame(T2 item1, T2 item2) {
+            return mWrappedCallback.areItemsTheSame(item1, item2);
+        }
+
+
+        /**
+         * This method dispatches any pending event notifications to the wrapped Callback.
+         * You <b>must</b> always call this method after you are done with editing the SortedList.
+         */
+        public void dispatchLastEvent() {
+            if (mLastEventType == TYPE_NONE) {
+                return;
+            }
+            switch (mLastEventType) {
+                case TYPE_ADD:
+                    mWrappedCallback.onInserted(mLastEventPosition, mLastEventCount);
+                    break;
+                case TYPE_REMOVE:
+                    mWrappedCallback.onRemoved(mLastEventPosition, mLastEventCount);
+                    break;
+                case TYPE_CHANGE:
+                    mWrappedCallback.onChanged(mLastEventPosition, mLastEventCount);
+                    break;
+            }
+            mLastEventType = TYPE_NONE;
+        }
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index 84070ab..032449c 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -559,6 +559,42 @@
         recycleUpdateOpsAndClearList(mPendingUpdates);
     }
 
+    public int applyPendingUpdatesToPosition(int position) {
+        final int size = mPendingUpdates.size();
+        for (int i = 0; i < size; i ++) {
+            UpdateOp op = mPendingUpdates.get(i);
+            switch (op.cmd) {
+                case UpdateOp.ADD:
+                    if (op.positionStart <= position) {
+                        position += op.itemCount;
+                    }
+                    break;
+                case UpdateOp.REMOVE:
+                    if (op.positionStart <= position) {
+                        final int end = op.positionStart + op.itemCount;
+                        if (end > position) {
+                            return RecyclerView.NO_POSITION;
+                        }
+                        position -= op.itemCount;
+                    }
+                    break;
+                case UpdateOp.MOVE:
+                    if (op.positionStart == position) {
+                        position = op.itemCount;//position end
+                    } else {
+                        if (op.positionStart < position) {
+                            position -= 1;
+                        }
+                        if (op.itemCount <= position) {
+                            position += 1;
+                        }
+                    }
+                    break;
+            }
+        }
+        return position;
+    }
+
     /**
      * Queued operation to happen when child views are updated.
      */
diff --git a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
index e555679..bf6014e 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
@@ -76,11 +76,11 @@
         } else {
             offset = getOffset(index);
         }
-        mCallback.addView(child, offset);
         mBucket.insert(offset, hidden);
         if (hidden) {
             mHiddenViews.add(child);
         }
+        mCallback.addView(child, offset);
         if (DEBUG) {
             Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
         }
@@ -117,10 +117,10 @@
         if (index < 0) {
             return;
         }
-        mCallback.removeViewAt(index);
         if (mBucket.remove(index)) {
             mHiddenViews.remove(view);
         }
+        mCallback.removeViewAt(index);
         if (DEBUG) {
             Log.d(TAG, "remove View off:" + index + "," + this);
         }
@@ -138,10 +138,10 @@
         if (view == null) {
             return;
         }
-        mCallback.removeViewAt(offset);
         if (mBucket.remove(offset)) {
             mHiddenViews.remove(view);
         }
+        mCallback.removeViewAt(offset);
         if (DEBUG) {
             Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
         }
@@ -161,9 +161,9 @@
      * Removes all views from the ViewGroup including the hidden ones.
      */
     void removeAllViewsUnfiltered() {
-        mCallback.removeAllViews();
         mBucket.reset();
         mHiddenViews.clear();
+        mCallback.removeAllViews();
         if (DEBUG) {
             Log.d(TAG, "removeAllViewsUnfiltered");
         }
@@ -181,7 +181,7 @@
         for (int i = 0; i < count; i++) {
             final View view = mHiddenViews.get(i);
             RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
-            if (holder.getPosition() == position && !holder.isInvalid() &&
+            if (holder.getLayoutPosition() == position && !holder.isInvalid() &&
                     (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
                 return view;
             }
@@ -205,8 +205,11 @@
         } else {
             offset = getOffset(index);
         }
-        mCallback.attachViewToParent(child, offset, layoutParams);
         mBucket.insert(offset, hidden);
+        if (hidden) {
+            mHiddenViews.add(child);
+        }
+        mCallback.attachViewToParent(child, offset, layoutParams);
         if (DEBUG) {
             Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
                     "h:" + hidden + ", " + this);
@@ -250,8 +253,8 @@
      */
     void detachViewFromParent(int index) {
         final int offset = getOffset(index);
-        mCallback.detachViewFromParent(offset);
         mBucket.remove(offset);
+        mCallback.detachViewFromParent(offset);
         if (DEBUG) {
             Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
         }
@@ -311,7 +314,7 @@
 
     @Override
     public String toString() {
-        return mBucket.toString();
+        return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
     }
 
     /**
@@ -330,11 +333,11 @@
         }
         if (mBucket.get(index)) {
             mBucket.remove(index);
-            mCallback.removeViewAt(index);
             if (!mHiddenViews.remove(view) && DEBUG) {
                 throw new IllegalStateException(
                         "removed a hidden view but it is not in hidden views list");
             }
+            mCallback.removeViewAt(index);
             return true;
         }
         return false;
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 2a27d65ab..950b254 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -331,31 +331,33 @@
 
     private void animateChangeImpl(final ChangeInfo changeInfo) {
         final ViewHolder holder = changeInfo.oldHolder;
-        final View view = holder.itemView;
+        final View view = holder == null ? null : holder.itemView;
         final ViewHolder newHolder = changeInfo.newHolder;
         final View newView = newHolder != null ? newHolder.itemView : null;
-        mChangeAnimations.add(changeInfo.oldHolder);
+        if (view != null) {
+            mChangeAnimations.add(changeInfo.oldHolder);
+            final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
+                    getChangeDuration());
+            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+            oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
+                @Override
+                public void onAnimationStart(View view) {
+                    dispatchChangeStarting(changeInfo.oldHolder, true);
+                }
 
-        final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
-                getChangeDuration());
-        oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
-        oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
-        oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
-            @Override
-            public void onAnimationStart(View view) {
-                dispatchChangeStarting(changeInfo.oldHolder, true);
-            }
-            @Override
-            public void onAnimationEnd(View view) {
-                oldViewAnim.setListener(null);
-                ViewCompat.setAlpha(view, 1);
-                ViewCompat.setTranslationX(view, 0);
-                ViewCompat.setTranslationY(view, 0);
-                dispatchChangeFinished(changeInfo.oldHolder, true);
-                mChangeAnimations.remove(changeInfo.oldHolder);
-                dispatchFinishedWhenDone();
-            }
-        }).start();
+                @Override
+                public void onAnimationEnd(View view) {
+                    oldViewAnim.setListener(null);
+                    ViewCompat.setAlpha(view, 1);
+                    ViewCompat.setTranslationX(view, 0);
+                    ViewCompat.setTranslationY(view, 0);
+                    dispatchChangeFinished(changeInfo.oldHolder, true);
+                    mChangeAnimations.remove(changeInfo.oldHolder);
+                    dispatchFinishedWhenDone();
+                }
+            }).start();
+        }
         if (newView != null) {
             mChangeAnimations.add(changeInfo.newHolder);
             final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
@@ -427,7 +429,7 @@
                 ViewCompat.setTranslationY(view, 0);
                 ViewCompat.setTranslationX(view, 0);
                 dispatchMoveFinished(item);
-                mPendingMoves.remove(item);
+                mPendingMoves.remove(i);
             }
         }
         endChangeAnimation(mPendingChanges, item);
@@ -444,7 +446,7 @@
             ArrayList<ChangeInfo> changes = mChangesList.get(i);
             endChangeAnimation(changes, item);
             if (changes.isEmpty()) {
-                mChangesList.remove(changes);
+                mChangesList.remove(i);
             }
         }
         for (int i = mMovesList.size() - 1; i >= 0; i--) {
@@ -457,7 +459,7 @@
                     dispatchMoveFinished(item);
                     moves.remove(j);
                     if (moves.isEmpty()) {
-                        mMovesList.remove(moves);
+                        mMovesList.remove(i);
                     }
                     break;
                 }
@@ -469,7 +471,7 @@
                 ViewCompat.setAlpha(view, 1);
                 dispatchAddFinished(item);
                 if (additions.isEmpty()) {
-                    mAdditionsList.remove(additions);
+                    mAdditionsList.remove(i);
                 }
             }
         }
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index a05ac47..383717e 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -129,7 +129,7 @@
             return;
         }
         LayoutParams glp = (LayoutParams) lp;
-        int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewPosition());
+        int spanGroupIndex = getSpanGroupIndex(recycler, state, glp.getViewLayoutPosition());
         if (mOrientation == HORIZONTAL) {
             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
                     glp.getSpanIndex(), glp.getSpanSize(),
@@ -164,7 +164,7 @@
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
-            final int viewPosition = lp.getViewPosition();
+            final int viewPosition = lp.getViewLayoutPosition();
             mPreLayoutSpanSizeCache.put(viewPosition, lp.getSpanSize());
             mPreLayoutSpanIndexCache.put(viewPosition, lp.getSpanIndex());
         }
@@ -753,6 +753,10 @@
 
     /**
      * LayoutParams used by GridLayoutManager.
+     * <p>
+     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
+     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
+     * expected to fill all of the space given to it.
      */
     public static class LayoutParams extends RecyclerView.LayoutParams {
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index fd8bd03..5f958c8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -17,9 +17,9 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.graphics.PointF;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.graphics.PointF;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -28,10 +28,10 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-
 import java.util.List;
 
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
 /**
  * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
  * similar functionality to {@link android.widget.ListView}.
@@ -217,6 +217,7 @@
         }
         SavedState state = new SavedState();
         if (getChildCount() > 0) {
+            ensureLayoutState();
             boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
             state.mAnchorLayoutFromEnd = didLayoutFromEnd;
             if (didLayoutFromEnd) {
@@ -460,8 +461,9 @@
         int extraForStart;
         int extraForEnd;
         final int extra = getExtraLayoutSpace(state);
-        boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition;
-        if (before == mShouldReverseLayout) {
+        // If the previous scroll delta was less than zero, the extra space should be laid out
+        // at the start. Otherwise, it should be at the end.
+        if (mLayoutState.mLastScrollDelta >= 0) {
             extraForEnd = extra;
             extraForStart = 0;
         } else {
@@ -599,7 +601,7 @@
         final int firstChildPos = getPosition(getChildAt(0));
         for (int i = 0; i < scrapSize; i++) {
             RecyclerView.ViewHolder scrap = scrapList.get(i);
-            final int position = scrap.getPosition();
+            final int position = scrap.getLayoutPosition();
             final int direction = position < firstChildPos != mShouldReverseLayout
                     ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
             if (direction == LayoutState.LAYOUT_START) {
@@ -665,18 +667,14 @@
         if (getChildCount() == 0) {
             return false;
         }
-        View focused = getFocusedChild();
-        if (focused != null && anchorInfo.assignFromViewIfValid(focused, state)) {
-            if (DEBUG) {
-                Log.d(TAG, "decided anchor child from focused view");
-            }
+        final View focused = getFocusedChild();
+        if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
+            anchorInfo.assignFromViewAndKeepVisibleRect(focused);
             return true;
         }
-
         if (mLastStackFromEnd != mStackFromEnd) {
             return false;
         }
-
         View referenceChild = anchorInfo.mLayoutFromEnd ? findReferenceChildClosestToEnd(state)
                 : findReferenceChildClosestToStart(state);
         if (referenceChild != null) {
@@ -990,27 +988,33 @@
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureLayoutState();
         return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
-                getChildClosestToStart(), getChildClosestToEnd(), this,
-                mSmoothScrollbarEnabled, mShouldReverseLayout);
+                findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+                findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
     }
 
     private int computeScrollExtent(RecyclerView.State state) {
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureLayoutState();
         return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
-                getChildClosestToStart(), getChildClosestToEnd(), this,
-                mSmoothScrollbarEnabled);
+                findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+                findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+                this,  mSmoothScrollbarEnabled);
     }
 
     private int computeScrollRange(RecyclerView.State state) {
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureLayoutState();
         return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
-                getChildClosestToStart(), getChildClosestToEnd(), this,
-                mSmoothScrollbarEnabled);
+                findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
+                findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
+                this, mSmoothScrollbarEnabled);
     }
 
     /**
@@ -1102,6 +1106,7 @@
         if (DEBUG) {
             Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
         }
+        mLayoutState.mLastScrollDelta = scrolled;
         return scrolled;
     }
 
@@ -1428,6 +1433,42 @@
         return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
     }
 
+    /**
+     * Convenience method to find the visible child closes to start. Caller should check if it has
+     * enough children.
+     *
+     * @param completelyVisible Whether child should be completely visible or not
+     * @return The first visible child closest to start of the layout from user's perspective.
+     */
+    private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
+            boolean acceptPartiallyVisible) {
+        if (mShouldReverseLayout) {
+            return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+                    acceptPartiallyVisible);
+        } else {
+            return findOneVisibleChild(0, getChildCount(), completelyVisible,
+                    acceptPartiallyVisible);
+        }
+    }
+
+    /**
+     * Convenience method to find the visible child closes to end. Caller should check if it has
+     * enough children.
+     *
+     * @param completelyVisible Whether child should be completely visible or not
+     * @return The first visible child closest to end of the layout from user's perspective.
+     */
+    private View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
+            boolean acceptPartiallyVisible) {
+        if (mShouldReverseLayout) {
+            return findOneVisibleChild(0, getChildCount(), completelyVisible,
+                    acceptPartiallyVisible);
+        } else {
+            return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
+                    acceptPartiallyVisible);
+        }
+    }
+
 
     /**
      * Among the children that are suitable to be considered as an anchor child, returns the one
@@ -1469,6 +1510,7 @@
     }
 
     private View findReferenceChild(int start, int end, int itemCount) {
+        ensureLayoutState();
         View invalidMatch = null;
         View outOfBoundsMatch = null;
         final int boundsStart = mOrientationHelper.getStartAfterPadding();
@@ -1496,7 +1538,8 @@
     }
 
     /**
-     * Returns the adapter position of the first visible view.
+     * Returns the adapter position of the first visible view. This position does not include
+     * adapter changes that were dispatched after the last layout pass.
      * <p>
      * Note that, this value is not affected by layout orientation or item order traversal.
      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
@@ -1513,12 +1556,13 @@
      * @see #findLastVisibleItemPosition()
      */
     public int findFirstVisibleItemPosition() {
-        final View child = findOneVisibleChild(0, getChildCount(), false);
+        final View child = findOneVisibleChild(0, getChildCount(), false, true);
         return child == null ? NO_POSITION : getPosition(child);
     }
 
     /**
-     * Returns the adapter position of the first fully visible view.
+     * Returns the adapter position of the first fully visible view. This position does not include
+     * adapter changes that were dispatched after the last layout pass.
      * <p>
      * Note that bounds check is only performed in the current orientation. That means, if
      * LayoutManager is horizontal, it will only check the view's left and right edges.
@@ -1529,12 +1573,13 @@
      * @see #findLastCompletelyVisibleItemPosition()
      */
     public int findFirstCompletelyVisibleItemPosition() {
-        final View child = findOneVisibleChild(0, getChildCount(), true);
+        final View child = findOneVisibleChild(0, getChildCount(), true, false);
         return child == null ? NO_POSITION : getPosition(child);
     }
 
     /**
-     * Returns the adapter position of the last visible view.
+     * Returns the adapter position of the last visible view. This position does not include
+     * adapter changes that were dispatched after the last layout pass.
      * <p>
      * Note that, this value is not affected by layout orientation or item order traversal.
      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
@@ -1551,12 +1596,13 @@
      * @see #findFirstVisibleItemPosition()
      */
     public int findLastVisibleItemPosition() {
-        final View child = findOneVisibleChild(getChildCount() - 1, -1, false);
+        final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
         return child == null ? NO_POSITION : getPosition(child);
     }
 
     /**
-     * Returns the adapter position of the last fully visible view.
+     * Returns the adapter position of the last fully visible view. This position does not include
+     * adapter changes that were dispatched after the last layout pass.
      * <p>
      * Note that bounds check is only performed in the current orientation. That means, if
      * LayoutManager is horizontal, it will only check the view's left and right edges.
@@ -1567,14 +1613,17 @@
      * @see #findFirstCompletelyVisibleItemPosition()
      */
     public int findLastCompletelyVisibleItemPosition() {
-        final View child = findOneVisibleChild(getChildCount() - 1, -1, true);
+        final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
         return child == null ? NO_POSITION : getPosition(child);
     }
 
-    View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
+    View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
+            boolean acceptPartiallyVisible) {
+        ensureLayoutState();
         final int start = mOrientationHelper.getStartAfterPadding();
         final int end = mOrientationHelper.getEndAfterPadding();
         final int next = toIndex > fromIndex ? 1 : -1;
+        View partiallyVisible = null;
         for (int i = fromIndex; i != toIndex; i+=next) {
             final View child = getChildAt(i);
             final int childStart = mOrientationHelper.getDecoratedStart(child);
@@ -1583,13 +1632,15 @@
                 if (completelyVisible) {
                     if (childStart >= start && childEnd <= end) {
                         return child;
+                    } else if (acceptPartiallyVisible && partiallyVisible == null) {
+                        partiallyVisible = child;
                     }
                 } else {
                     return child;
                 }
             }
         }
-        return null;
+        return partiallyVisible;
     }
 
     @Override
@@ -1604,6 +1655,7 @@
         if (layoutDir == LayoutState.INVALID_LAYOUT) {
             return null;
         }
+        ensureLayoutState();
         final View referenceChild;
         if (layoutDir == LayoutState.LAYOUT_START) {
             referenceChild = findReferenceChildClosestToStart(state);
@@ -1778,6 +1830,11 @@
         boolean mIsPreLayout = false;
 
         /**
+         * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} amount.
+         */
+        int mLastScrollDelta;
+
+        /**
          * When LLM needs to layout particular views, it sets this list in which case, LayoutState
          * will only return views from this list and return null if it cannot find an item.
          */
@@ -1821,7 +1878,8 @@
                 if (!mIsPreLayout && viewHolder.isRemoved()) {
                     continue;
                 }
-                final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection;
+                final int distance = (viewHolder.getLayoutPosition() - mCurrentPosition) *
+                        mItemDirection;
                 if (distance < 0) {
                     continue; // item is not in current direction
                 }
@@ -1837,7 +1895,7 @@
                 Log.d(TAG, "layout from scrap. found view:?" + (closest != null));
             }
             if (closest != null) {
-                mCurrentPosition = closest.getPosition() + mItemDirection;
+                mCurrentPosition = closest.getLayoutPosition() + mItemDirection;
                 return closest.itemView;
             }
             return null;
@@ -1944,15 +2002,66 @@
          * child.
          */
         public boolean assignFromViewIfValid(View child, RecyclerView.State state) {
-            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
-            if (!lp.isItemRemoved() && lp.getViewPosition() >= 0
-                    && lp.getViewPosition() < state.getItemCount()) {
+            if (isViewValidAsAnchor(child, state)) {
                 assignFromView(child);
                 return true;
             }
             return false;
         }
 
+        private boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
+            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+            return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
+                    && lp.getViewLayoutPosition() < state.getItemCount();
+        }
+
+        public void assignFromViewAndKeepVisibleRect(View child) {
+            final int spaceChange = mOrientationHelper.getTotalSpaceChange();
+            if (spaceChange >= 0) {
+                assignFromView(child);
+                return;
+            }
+            mPosition = getPosition(child);
+            if (mLayoutFromEnd) {
+                final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
+                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+                final int previousEndMargin = prevLayoutEnd - childEnd;
+                mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin;
+                // ensure we did not push child's top out of bounds because of this
+                if (previousEndMargin > 0) {// we have room to shift bottom if necessary
+                    final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+                    final int estimatedChildStart = mCoordinate - childSize;
+                    final int layoutStart = mOrientationHelper.getStartAfterPadding();
+                    final int previousStartMargin = mOrientationHelper.getDecoratedStart(child) -
+                            layoutStart;
+                    final int startReference = layoutStart + Math.min(previousStartMargin, 0);
+                    final int startMargin = estimatedChildStart - startReference;
+                    if (startMargin < 0) {
+                        // offset to make top visible but not too much
+                        mCoordinate += Math.min(previousEndMargin, -startMargin);
+                    }
+                }
+            } else {
+                final int childStart = mOrientationHelper.getDecoratedStart(child);
+                final int startMargin = childStart - mOrientationHelper.getStartAfterPadding();
+                mCoordinate = childStart;
+                if (startMargin > 0) { // we have room to fix end as well
+                    final int estimatedEnd = childStart +
+                            mOrientationHelper.getDecoratedMeasurement(child);
+                    final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding() -
+                            spaceChange;
+                    final int previousEndMargin = previousLayoutEnd -
+                            mOrientationHelper.getDecoratedEnd(child);
+                    final int endReference = mOrientationHelper.getEndAfterPadding() -
+                            Math.min(0, previousEndMargin);
+                    final int endMargin = endReference - estimatedEnd;
+                    if (endMargin < 0) {
+                        mCoordinate -= Math.min(startMargin, -endMargin);
+                    }
+                }
+            }
+        }
+
         public void assignFromView(View child) {
             if (mLayoutFromEnd) {
                 mCoordinate = mOrientationHelper.getDecoratedEnd(child) +
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 149a1df..496613d 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -28,9 +28,12 @@
 import android.os.Parcelable;
 import android.support.annotation.Nullable;
 import android.support.v4.util.ArrayMap;
+import android.support.v4.view.InputDeviceCompat;
 import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ScrollingView;
 import android.support.v4.view.VelocityTrackerCompat;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewConfigurationCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -43,6 +46,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.TypedValue;
 import android.view.FocusFinder;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -82,21 +86,61 @@
  *     <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
  *     being displayed.</li>
  * </ul>
+ *
+ * <h4>Positions in RecyclerView:</h4>
+ * <p>
+ * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
+ * {@link LayoutManager} to be able to detect data set changes in batches during a layout
+ * calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
+ * It also helps with performance because all view bindings happen at the same time and unnecessary
+ * bindings are avoided.
+ * <p>
+ * For this reason, there are two types of <code>position</code> related methods in RecyclerView:
+ * <ul>
+ *     <li>layout position: Position of an item in the latest layout calculation. This is the
+ *     position from the LayoutManager's perspective.</li>
+ *     <li>adapter position: Position of an item in the adapter. This is the position from
+ *     the Adapter's perspective.</li>
+ * </ul>
+ * <p>
+ * These two positions are the same except the time between dispatching <code>adapter.notify*
+ * </code> events and calculating the updated layout.
+ * <p>
+ * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest
+ * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
+ * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
+ * last layout calculation. You can rely on these positions to be consistent with what user is
+ * currently seeing on the screen. For example, if you have a list of items on the screen and user
+ * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user
+ * is seeing.
+ * <p>
+ * The other set of position related methods are in the form of
+ * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()},
+ * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
+ * work with up-to-date adapter positions even if they may not have been reflected to layout yet.
+ * For example, if you want to access the item in the adapter on a ViewHolder click, you should use
+ * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
+ * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
+ * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
+ * <code>null</code> results from these methods.
+ * <p>
+ * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
+ * writing an {@link Adapter}, you probably want to use adapter positions.
  */
-public class RecyclerView extends ViewGroup {
+public class RecyclerView extends ViewGroup implements ScrollingView {
     private static final String TAG = "RecyclerView";
 
     private static final boolean DEBUG = false;
 
     /**
-     * On Kitkat, there is a bug which prevents DisplayList from being invalidated if a View is two
-     * levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by setting
-     * View's visibility to INVISIBLE when View is detached. On Kitkat, Recycler recursively
-     * traverses itemView and invalidates display list for each ViewGroup that matches this
-     * criteria.
+     * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
+     * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
+     * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler
+     * recursively traverses itemView and invalidates display list for each ViewGroup that matches
+     * this criteria.
      */
-    private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 19 ||
-            Build.VERSION.SDK_INT == 20;
+    private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
+            || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
 
     private static final boolean DISPATCH_TEMP_DETACH = false;
     public static final int HORIZONTAL = 0;
@@ -106,6 +150,20 @@
     public static final long NO_ID = -1;
     public static final int INVALID_TYPE = -1;
 
+    /**
+     * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+     * that the RecyclerView should use the standard touch slop for smooth,
+     * continuous scrolling.
+     */
+    public static final int TOUCH_SLOP_DEFAULT = 0;
+
+    /**
+     * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
+     * that the RecyclerView should use the standard touch slop for scrolling
+     * widgets that snap to a page or other coarse-grained barrier.
+     */
+    public static final int TOUCH_SLOP_PAGING = 1;
+
     private static final int MAX_SCROLL_DURATION = 2000;
 
     private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
@@ -135,16 +193,13 @@
      */
     private final Runnable mUpdateChildViewsRunnable = new Runnable() {
         public void run() {
-            if (!mAdapterHelper.hasPendingUpdates()) {
-                return;
-            }
             if (!mFirstLayoutComplete) {
                 // a layout request will happen, we should not do layout here.
                 return;
             }
             if (mDataSetHasChangedAfterLayout) {
                 dispatchLayout();
-            } else {
+            } else if (mAdapterHelper.hasPendingUpdates()) {
                 eatRequestLayout();
                 mAdapterHelper.preProcess();
                 if (!mLayoutRequestEaten) {
@@ -224,9 +279,11 @@
     private int mInitialTouchY;
     private int mLastTouchX;
     private int mLastTouchY;
-    private final int mTouchSlop;
+    private int mTouchSlop;
     private final int mMinFlingVelocity;
     private final int mMaxFlingVelocity;
+    // This value is used when handling generic motion events.
+    private float mScrollFactor = Float.MIN_VALUE;
 
     private final ViewFlinger mViewFlinger = new ViewFlinger();
 
@@ -241,6 +298,11 @@
             new ItemAnimatorRestoreListener();
     private boolean mPostedAnimatorRunner = false;
     private RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
+
+    // simple array to keep min and max child position during a layout calculation
+    // preserved not to create a new one in each layout pass
+    private final int[] mMinMaxLayoutPositions = new int[2];
+
     private Runnable mItemAnimatorRunner = new Runnable() {
         @Override
         public void run() {
@@ -268,7 +330,7 @@
 
     public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-
+        setFocusableInTouchMode(true);
         final int version = Build.VERSION.SDK_INT;
         mPostUpdatesOnAnimation = version >= 16;
 
@@ -359,11 +421,36 @@
             @Override
             public void attachViewToParent(View child, int index,
                     ViewGroup.LayoutParams layoutParams) {
+                final ViewHolder vh = getChildViewHolderInt(child);
+                if (vh != null) {
+                    if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
+                        throw new IllegalArgumentException("Called attach on a child which is not"
+                                + " detached: " + vh);
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "reAttach " + vh);
+                    }
+                    vh.clearTmpDetachFlag();
+                }
                 RecyclerView.this.attachViewToParent(child, index, layoutParams);
             }
 
             @Override
             public void detachViewFromParent(int offset) {
+                final View view = getChildAt(offset);
+                if (view != null) {
+                    final ViewHolder vh = getChildViewHolderInt(view);
+                    if (vh != null) {
+                        if (vh.isTmpDetached() && !vh.shouldIgnore()) {
+                            throw new IllegalArgumentException("called detach on an already"
+                                    + " detached child " + vh);
+                        }
+                        if (DEBUG) {
+                            Log.d(TAG, "tmpDetach " + vh);
+                        }
+                        vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
+                    }
+                }
                 RecyclerView.this.detachViewFromParent(offset);
             }
         });
@@ -373,7 +460,19 @@
         mAdapterHelper = new AdapterHelper(new Callback() {
             @Override
             public ViewHolder findViewHolder(int position) {
-                return findViewHolderForPosition(position, true);
+                final ViewHolder vh = findViewHolderForPosition(position, true);
+                if (vh == null) {
+                    return null;
+                }
+                // ensure it is not hidden because for adapter helper, the only thing matter is that
+                // LM thinks view is a child.
+                if (mChildHelper.isHidden(vh.itemView)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "assuming view holder cannot be find because it is hidden");
+                    }
+                    return null;
+                }
+                return vh;
             }
 
             @Override
@@ -469,6 +568,32 @@
     }
 
     /**
+     * Configure the scrolling touch slop for a specific use case.
+     *
+     * Set up the RecyclerView's scrolling motion threshold based on common usages.
+     * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
+     *
+     * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing
+     *                     the intended usage of this RecyclerView
+     */
+    public void setScrollingTouchSlop(int slopConstant) {
+        final ViewConfiguration vc = ViewConfiguration.get(getContext());
+        switch (slopConstant) {
+            default:
+                Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
+                      + slopConstant + "; using default value");
+                // fall-through
+            case TOUCH_SLOP_DEFAULT:
+                mTouchSlop = vc.getScaledTouchSlop();
+                break;
+
+            case TOUCH_SLOP_PAGING:
+                mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);
+                break;
+        }
+    }
+
+    /**
      * Swaps the current adapter with the provided one. It is similar to
      * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
      * {@link ViewHolder} and does not clear the RecycledViewPool.
@@ -484,7 +609,7 @@
      */
     public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
         setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
-        mDataSetHasChangedAfterLayout = true;
+        setDataSetChangedAfterLayout();
         requestLayout();
     }
     /**
@@ -514,6 +639,7 @@
             boolean removeAndRecycleViews) {
         if (mAdapter != null) {
             mAdapter.unregisterAdapterDataObserver(mObserver);
+            mAdapter.onDetachedFromRecyclerView(this);
         }
         if (!compatibleWithPrevious || removeAndRecycleViews) {
             // end all running animations
@@ -526,14 +652,17 @@
             // count.
             if (mLayout != null) {
                 mLayout.removeAndRecycleAllViews(mRecycler);
-                mLayout.removeAndRecycleScrapInt(mRecycler, true);
+                mLayout.removeAndRecycleScrapInt(mRecycler);
             }
+            // we should clear it here before adapters are swapped to ensure correct callbacks.
+            mRecycler.clear();
         }
         mAdapterHelper.reset();
         final Adapter oldAdapter = mAdapter;
         mAdapter = adapter;
         if (adapter != null) {
             adapter.registerAdapterDataObserver(mObserver);
+            adapter.onAttachedToRecyclerView(this);
         }
         if (mLayout != null) {
             mLayout.onAdapterChanged(oldAdapter, mAdapter);
@@ -636,12 +765,16 @@
      * purely for the purpose of being animated out of view. They are drawn as a regular
      * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
      * as they are managed separately from the regular child views.
-     * @param view The view to be removed
+     * @param viewHolder The ViewHolder to be removed
      */
-    private void addAnimatingView(View view) {
+    private void addAnimatingView(ViewHolder viewHolder) {
+        final View view = viewHolder.itemView;
         final boolean alreadyParented = view.getParent() == this;
         mRecycler.unscrapView(getChildViewHolder(view));
-        if (!alreadyParented) {
+        if (viewHolder.isTmpDetached()) {
+            // re-attach
+            mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
+        } else if(!alreadyParented) {
             mChildHelper.addView(view, true);
         } else {
             mChildHelper.hide(view);
@@ -651,11 +784,13 @@
     /**
      * Removes a view from the animatingViews list.
      * @param view The view to be removed
-     * @see #addAnimatingView(View)
+     * @see #addAnimatingView(RecyclerView.ViewHolder)
+     * @return true if an animating view is removed
      */
-    private void removeAnimatingView(View view) {
+    private boolean removeAnimatingView(View view) {
         eatRequestLayout();
-        if (mChildHelper.removeViewIfHidden(view)) {
+        final boolean removed = mChildHelper.removeViewIfHidden(view);
+        if (removed) {
             final ViewHolder viewHolder = getChildViewHolderInt(view);
             mRecycler.unscrapView(viewHolder);
             mRecycler.recycleViewHolderInternal(viewHolder);
@@ -664,6 +799,7 @@
             }
         }
         resumeRequestLayout(false);
+        return removed;
     }
 
     /**
@@ -750,7 +886,9 @@
         if (mScrollListener != null) {
             mScrollListener.onScrollStateChanged(this, state);
         }
-        mLayout.onScrollStateChanged(state);
+        if (mLayout != null) {
+            mLayout.onScrollStateChanged(state);
+        }
     }
 
     /**
@@ -841,6 +979,11 @@
      */
     public void scrollToPosition(int position) {
         stopScroll();
+        if (mLayout == null) {
+            Log.e(TAG, "Cannot scroll to position a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+            return;
+        }
         mLayout.scrollToPosition(position);
         awakenScrollBars();
     }
@@ -861,6 +1004,11 @@
      * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
      */
     public void smoothScrollToPosition(int position) {
+        if (mLayout == null) {
+            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+            return;
+        }
         mLayout.smoothScrollToPosition(this, mState, position);
     }
 
@@ -873,8 +1021,9 @@
     @Override
     public void scrollBy(int x, int y) {
         if (mLayout == null) {
-            throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
+            Log.e(TAG, "Cannot scroll without a LayoutManager set. " +
                     "Call setLayoutManager with a non-null argument.");
+            return;
         }
         final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
         final boolean canScrollVertical = mLayout.canScrollVertically();
@@ -892,15 +1041,15 @@
      * This method consumes all deferred changes to avoid that case.
      */
     private void consumePendingUpdateOperations() {
-        if (mAdapterHelper.hasPendingUpdates()) {
-            mUpdateChildViewsRunnable.run();
-        }
+        mUpdateChildViewsRunnable.run();
     }
 
     /**
      * Does not perform bounds checking. Used by internal methods that have already validated input.
+     *
+     * @return Whether any scroll was consumed in either direction.
      */
-    void scrollByInternal(int x, int y) {
+    boolean scrollByInternal(int x, int y) {
         int overscrollX = 0, overscrollY = 0;
         int hresult = 0, vresult = 0;
         consumePendingUpdateOperations();
@@ -947,14 +1096,12 @@
             pullGlows(overscrollX, overscrollY);
         }
         if (hresult != 0 || vresult != 0) {
-            onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
-            if (mScrollListener != null) {
-                mScrollListener.onScrolled(this, hresult, vresult);
-            }
+            notifyOnScrolled(hresult, vresult);
         }
         if (!awakenScrollBars()) {
             invalidate();
         }
+        return hresult != 0 || vresult != 0;
     }
 
     /**
@@ -976,7 +1123,7 @@
      * (RecyclerView.Adapter)
      */
     @Override
-    protected int computeHorizontalScrollOffset() {
+    public int computeHorizontalScrollOffset() {
         return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState)
                 : 0;
     }
@@ -999,7 +1146,7 @@
      * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
      */
     @Override
-    protected int computeHorizontalScrollExtent() {
+    public int computeHorizontalScrollExtent() {
         return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
     }
 
@@ -1019,7 +1166,7 @@
      * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
      */
     @Override
-    protected int computeHorizontalScrollRange() {
+    public int computeHorizontalScrollRange() {
         return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
     }
 
@@ -1041,7 +1188,7 @@
      * (RecyclerView.Adapter)
      */
     @Override
-    protected int computeVerticalScrollOffset() {
+    public int computeVerticalScrollOffset() {
         return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
     }
 
@@ -1062,7 +1209,7 @@
      * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
      */
     @Override
-    protected int computeVerticalScrollExtent() {
+    public int computeVerticalScrollExtent() {
         return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
     }
 
@@ -1082,7 +1229,7 @@
      * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
      */
     @Override
-    protected int computeVerticalScrollRange() {
+    public int computeVerticalScrollRange() {
         return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
     }
 
@@ -1112,6 +1259,17 @@
      * @param dy Pixels to scroll vertically
      */
     public void smoothScrollBy(int dx, int dy) {
+        if (mLayout == null) {
+            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+            return;
+        }
+        if (!mLayout.canScrollHorizontally()) {
+            dx = 0;
+        }
+        if (!mLayout.canScrollVertically()) {
+            dy = 0;
+        }
         if (dx != 0 || dy != 0) {
             mViewFlinger.smoothScrollBy(dx, dy);
         }
@@ -1124,13 +1282,24 @@
      *
      * @param velocityX Initial horizontal velocity in pixels per second
      * @param velocityY Initial vertical velocity in pixels per second
-     * @return true if the fling was started, false if the velocity was too low to fling
+     * @return true if the fling was started, false if the velocity was too low to fling or
+     * LayoutManager does not support scrolling in the axis fling is issued.
+     *
+     * @see LayoutManager#canScrollVertically()
+     * @see LayoutManager#canScrollHorizontally()
      */
     public boolean fling(int velocityX, int velocityY) {
-        if (Math.abs(velocityX) < mMinFlingVelocity) {
+        if (mLayout == null) {
+            Log.e(TAG, "Cannot fling without a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+            return false;
+        }
+        final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+        final boolean canScrollVertical = mLayout.canScrollVertically();
+        if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
             velocityX = 0;
         }
-        if (Math.abs(velocityY) < mMinFlingVelocity) {
+        if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
             velocityY = 0;
         }
         velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
@@ -1156,7 +1325,9 @@
      */
     private void stopScrollersInternal() {
         mViewFlinger.stop();
-        mLayout.stopSmoothScroller();
+        if (mLayout != null) {
+            mLayout.stopSmoothScroller();
+        }
     }
 
     /**
@@ -1303,7 +1474,7 @@
         }
         final FocusFinder ff = FocusFinder.getInstance();
         result = ff.findNextFocus(this, focused, direction);
-        if (result == null && mAdapter != null) {
+        if (result == null && mAdapter != null && mLayout != null) {
             eatRequestLayout();
             result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
             resumeRequestLayout(false);
@@ -1315,6 +1486,23 @@
     public void requestChildFocus(View child, View focused) {
         if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) {
             mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+
+            // get item decor offsets w/o refreshing. If they are invalid, there will be another
+            // layout pass to fix them, then it is LayoutManager's responsibility to keep focused
+            // View in viewport.
+            final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams();
+            if (focusedLayoutParams instanceof LayoutParams) {
+                // if focused child has item decors, use them. Otherwise, ignore.
+                final LayoutParams lp = (LayoutParams) focusedLayoutParams;
+                if (!lp.mInsetsDirty) {
+                    final Rect insets = lp.mDecorInsets;
+                    mTempRect.left -= insets.left;
+                    mTempRect.right += insets.right;
+                    mTempRect.top -= insets.top;
+                    mTempRect.bottom += insets.bottom;
+                }
+            }
+
             offsetDescendantRectToMyCoords(focused, mTempRect);
             offsetRectIntoDescendantCoords(child, mTempRect);
             requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
@@ -1531,7 +1719,6 @@
                         startScroll = true;
                     }
                     if (startScroll) {
-                        getParent().requestDisallowInterceptTouchEvent(true);
                         setScrollState(SCROLL_STATE_DRAGGING);
                     }
                 }
@@ -1606,15 +1793,16 @@
                         startScroll = true;
                     }
                     if (startScroll) {
-                        getParent().requestDisallowInterceptTouchEvent(true);
                         setScrollState(SCROLL_STATE_DRAGGING);
                     }
                 }
                 if (mScrollState == SCROLL_STATE_DRAGGING) {
                     final int dx = x - mLastTouchX;
                     final int dy = y - mLastTouchY;
-                    scrollByInternal(canScrollHorizontally ? -dx : 0,
-                            canScrollVertically ? -dy : 0);
+                    if (scrollByInternal(
+                            canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) {
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                    }
                 }
                 mLastTouchX = x;
                 mLastTouchY = y;
@@ -1664,6 +1852,54 @@
         }
     }
 
+    // @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        if (mLayout == null) {
+            return false;
+        }
+        if ((MotionEventCompat.getSource(event) & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) {
+            if (event.getAction() == MotionEventCompat.ACTION_SCROLL) {
+                final float vScroll, hScroll;
+                if (mLayout.canScrollVertically()) {
+                    vScroll = MotionEventCompat
+                            .getAxisValue(event, MotionEventCompat.AXIS_VSCROLL);
+                } else {
+                    vScroll = 0f;
+                }
+                if (mLayout.canScrollHorizontally()) {
+                    hScroll = MotionEventCompat
+                            .getAxisValue(event, MotionEventCompat.AXIS_HSCROLL);
+                } else {
+                    hScroll = 0f;
+                }
+
+                if (vScroll != 0 || hScroll != 0) {
+                    final float scrollFactor = getScrollFactor();
+                    scrollBy((int) (hScroll * scrollFactor), (int) (vScroll * scrollFactor));
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Ported from View.getVerticalScrollFactor.
+     */
+    private float getScrollFactor() {
+        if (mScrollFactor == Float.MIN_VALUE) {
+            TypedValue outValue = new TypedValue();
+            if (getContext().getTheme().resolveAttribute(
+                    android.R.attr.listPreferredItemHeight, outValue, true)) {
+                mScrollFactor = outValue.getDimension(
+                        getContext().getResources().getDisplayMetrics());
+            } else {
+                return 0; //listPreferredItemHeight is not defined, no generic scrolling
+            }
+
+        }
+        return mScrollFactor;
+    }
+
     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
         if (mAdapterUpdateDuringMeasure) {
@@ -1690,11 +1926,52 @@
         } else {
             mState.mItemCount = 0;
         }
+        if (mLayout == null) {
+            defaultOnMeasure(widthSpec, heightSpec);
+        } else {
+            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+        }
 
-        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
         mState.mInPreLayout = false; // clear
     }
 
+    /**
+     * Used when onMeasure is called before layout manager is set
+     */
+    private void defaultOnMeasure(int widthSpec, int heightSpec) {
+        final int widthMode = MeasureSpec.getMode(widthSpec);
+        final int heightMode = MeasureSpec.getMode(heightSpec);
+        final int widthSize = MeasureSpec.getSize(widthSpec);
+        final int heightSize = MeasureSpec.getSize(heightSpec);
+
+        int width = 0;
+        int height = 0;
+
+        switch (widthMode) {
+            case MeasureSpec.EXACTLY:
+            case MeasureSpec.AT_MOST:
+                width = widthSize;
+                break;
+            case MeasureSpec.UNSPECIFIED:
+            default:
+                width = ViewCompat.getMinimumWidth(this);
+                break;
+        }
+
+        switch (heightMode) {
+            case MeasureSpec.EXACTLY:
+            case MeasureSpec.AT_MOST:
+                height = heightSize;
+                break;
+            case MeasureSpec.UNSPECIFIED:
+            default:
+                height = ViewCompat.getMinimumHeight(this);
+                break;
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -1817,6 +2094,10 @@
             Log.e(TAG, "No adapter attached; skipping layout");
             return;
         }
+        if (mLayout == null) {
+            Log.e(TAG, "No layout manager attached; skipping layout");
+            return;
+        }
         mDisappearingViewsInLayoutPass.clear();
         eatRequestLayout();
         mRunningLayoutOrScroll = true;
@@ -1829,6 +2110,7 @@
         ArrayMap<View, Rect> appearingViewInitialBounds = null;
         mState.mInPreLayout = mState.mRunPredictiveAnimations;
         mState.mItemCount = mAdapter.getItemCount();
+        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
 
         if (mState.mRunSimpleAnimations) {
             // Step 0: Find out where all non-removed items are, pre-layout
@@ -1952,9 +2234,7 @@
                     mState.mPreLayoutHolderMap.removeAt(i);
 
                     View disappearingItemView = disappearingItem.holder.itemView;
-                    removeDetachedView(disappearingItemView, false);
                     mRecycler.unscrapView(disappearingItem.holder);
-
                     animateDisappearance(disappearingItem);
                 }
             }
@@ -2015,7 +2295,7 @@
             }
         }
         resumeRequestLayout(false);
-        mLayout.removeAndRecycleScrapInt(mRecycler, !mState.mRunPredictiveAnimations);
+        mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
         mState.mRunSimpleAnimations = false;
@@ -2026,6 +2306,69 @@
             mRecycler.mChangedScrap.clear();
         }
         mState.mOldChangedHolders = null;
+
+        if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
+            notifyOnScrolled(0, 0);
+        }
+    }
+
+    private void findMinMaxChildLayoutPositions(int[] into) {
+        final int count = mChildHelper.getChildCount();
+        if (count == 0) {
+            into[0] = 0;
+            into[1] = 0;
+            return;
+        }
+        int minPositionPreLayout = Integer.MAX_VALUE;
+        int maxPositionPreLayout = Integer.MIN_VALUE;
+        for (int i = 0; i < count; ++i) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+            if (holder.shouldIgnore()) {
+                continue;
+            }
+            final int pos = holder.getLayoutPosition();
+            if (pos < minPositionPreLayout) {
+                minPositionPreLayout = pos;
+            }
+            if (pos > maxPositionPreLayout) {
+                maxPositionPreLayout = pos;
+            }
+        }
+        into[0] = minPositionPreLayout;
+        into[1] = maxPositionPreLayout;
+    }
+
+    private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
+        int count = mChildHelper.getChildCount();
+        if (count == 0) {
+            return minPositionPreLayout != 0 || maxPositionPreLayout != 0;
+        }
+        for (int i = 0; i < count; ++i) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+            if (holder.shouldIgnore()) {
+                continue;
+            }
+            final int pos = holder.getLayoutPosition();
+            if (pos < minPositionPreLayout || pos > maxPositionPreLayout) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void removeDetachedView(View child, boolean animate) {
+        ViewHolder vh = getChildViewHolderInt(child);
+        if (vh != null) {
+            if (vh.isTmpDetached()) {
+                vh.clearTmpDetachFlag();
+            } else if (!vh.shouldIgnore()) {
+                throw new IllegalArgumentException("Called removeDetachedView with a view which"
+                        + " is not flagged as tmp detached." + vh);
+            }
+        }
+        dispatchChildDetached(child);
+        super.removeDetachedView(child, animate);
     }
 
     /**
@@ -2092,7 +2435,7 @@
 
     private void animateDisappearance(ItemHolderInfo disappearingItem) {
         View disappearingItemView = disappearingItem.holder.itemView;
-        addAnimatingView(disappearingItemView);
+        addAnimatingView(disappearingItem.holder);
         int oldLeft = disappearingItem.left;
         int oldTop = disappearingItem.top;
         int newLeft = disappearingItemView.getLeft();
@@ -2124,8 +2467,7 @@
 
     private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) {
         oldHolder.setIsRecyclable(false);
-        removeDetachedView(oldHolder.itemView, false);
-        addAnimatingView(oldHolder.itemView);
+        addAnimatingView(oldHolder);
         oldHolder.mShadowedHolder = newHolder;
         mRecycler.unscrapView(oldHolder);
         if (DEBUG) {
@@ -2436,13 +2778,28 @@
                 } else {
                     // binding to a new view will need re-layout anyways. We can as well trigger
                     // it here so that it happens during layout
-                    holder.addFlags(ViewHolder.FLAG_INVALID);
                     requestLayout();
+                    break;
                 }
             }
         }
     }
 
+    private void setDataSetChangedAfterLayout() {
+        if (mDataSetHasChangedAfterLayout) {
+            return;
+        }
+        mDataSetHasChangedAfterLayout = true;
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.shouldIgnore()) {
+                holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+            }
+        }
+        mRecycler.setAdapterPositionsAsUnknown();
+    }
+
     /**
      * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
      * data change event.
@@ -2498,14 +2855,38 @@
     }
 
     /**
+     * @deprecated use {@link #getChildAdapterPosition(View)} or
+     * {@link #getChildLayoutPosition(View)}.
+     */
+    @Deprecated
+    public int getChildPosition(View child) {
+        return getChildAdapterPosition(child);
+    }
+
+    /**
      * Return the adapter position that the given child view corresponds to.
      *
      * @param child Child View to query
      * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
      */
-    public int getChildPosition(View child) {
+    public int getChildAdapterPosition(View child) {
         final ViewHolder holder = getChildViewHolderInt(child);
-        return holder != null ? holder.getPosition() : NO_POSITION;
+        return holder != null ? holder.getAdapterPosition() : NO_POSITION;
+    }
+
+    /**
+     * Return the adapter position of the given child view as of the latest completed layout pass.
+     * <p>
+     * This position may not be equal to Item's adapter position if there are pending changes
+     * in the adapter which have not been reflected to the layout yet.
+     *
+     * @param child Child View to query
+     * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
+     * the View is representing a removed item.
+     */
+    public int getChildLayoutPosition(View child) {
+        final ViewHolder holder = getChildViewHolderInt(child);
+        return holder != null ? holder.getLayoutPosition() : NO_POSITION;
     }
 
     /**
@@ -2523,15 +2904,61 @@
     }
 
     /**
-     * Return the ViewHolder for the item in the given position of the data set.
-     *
-     * @param position The position of the item in the data set of the adapter
-     * @return The ViewHolder at <code>position</code>
+     * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or
+     * {@link #findViewHolderForAdapterPosition(int)}
      */
+    @Deprecated
     public ViewHolder findViewHolderForPosition(int position) {
         return findViewHolderForPosition(position, false);
     }
 
+    /**
+     * Return the ViewHolder for the item in the given position of the data set as of the latest
+     * layout pass.
+     * <p>
+     * This method checks only the children of RecyclerView. If the item at the given
+     * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+     * <p>
+     * Note that when Adapter contents change, ViewHolder positions are not updated until the
+     * next layout calculation. If there are pending adapter updates, the return value of this
+     * method may not match your adapter contents. You can use
+     * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+     *
+     * @param position The position of the item in the data set of the adapter
+     * @return The ViewHolder at <code>position</code> or null if there is no such item
+     */
+    public ViewHolder findViewHolderForLayoutPosition(int position) {
+        return findViewHolderForPosition(position, false);
+    }
+
+    /**
+     * Return the ViewHolder for the item in the given position of the data set. Unlike
+     * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
+     * adapter changes that may not be reflected to the layout yet. On the other hand, if
+     * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
+     * calculated yet, this method will return <code>null</code> since the new positions of views
+     * are unknown until the layout is calculated.
+     * <p>
+     * This method checks only the children of RecyclerView. If the item at the given
+     * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+     *
+     * @param position The position of the item in the data set of the adapter
+     * @return The ViewHolder at <code>position</code> or null if there is no such item
+     */
+    public ViewHolder findViewHolderForAdapterPosition(int position) {
+        if (mDataSetHasChangedAfterLayout) {
+            return null;
+        }
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) {
+                return holder;
+            }
+        }
+        return null;
+    }
+
     ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
         final int childCount = mChildHelper.getUnfilteredChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -2541,7 +2968,7 @@
                     if (holder.mPosition == position) {
                         return holder;
                     }
-                } else if (holder.getPosition() == position) {
+                } else if (holder.getLayoutPosition() == position) {
                     return holder;
                 }
             }
@@ -2556,10 +2983,12 @@
      * Return the ViewHolder for the item with the given id. The RecyclerView must
      * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
      * return a non-null value.
+     * <p>
+     * This method checks only the children of RecyclerView. If the item with the given
+     * <code>id</code> is not laid out, it <em>will not</em> create a new one.
      *
      * @param id The id for the requested item
-     * @return The ViewHolder with the given <code>id</code>, of null if there
-     * is no such item.
+     * @return The ViewHolder with the given <code>id</code> or null if there is no such item
      */
     public ViewHolder findViewHolderForItemId(long id) {
         final int childCount = mChildHelper.getUnfilteredChildCount();
@@ -2722,17 +3151,14 @@
                             View view = mChildHelper.getChildAt(i);
                             ViewHolder holder = getChildViewHolder(view);
                             if (holder != null && holder.mShadowingHolder != null) {
-                                View shadowingView = holder.mShadowingHolder != null ?
-                                        holder.mShadowingHolder.itemView : null;
-                                if (shadowingView != null) {
-                                    int left = view.getLeft();
-                                    int top = view.getTop();
-                                    if (left != shadowingView.getLeft() ||
-                                            top != shadowingView.getTop()) {
-                                        shadowingView.layout(left, top,
-                                                left + shadowingView.getWidth(),
-                                                top + shadowingView.getHeight());
-                                    }
+                                View shadowingView = holder.mShadowingHolder.itemView;
+                                int left = view.getLeft();
+                                int top = view.getTop();
+                                if (left != shadowingView.getLeft() ||
+                                        top != shadowingView.getTop()) {
+                                    shadowingView.layout(left, top,
+                                            left + shadowingView.getWidth(),
+                                            top + shadowingView.getHeight());
                                 }
                             }
                         }
@@ -2753,7 +3179,6 @@
                     mRunningLayoutOrScroll = false;
                     resumeRequestLayout(false);
                 }
-                final boolean fullyConsumedScroll = dx == hresult && dy == vresult;
                 if (!mItemDecorations.isEmpty()) {
                     invalidate();
                 }
@@ -2784,18 +3209,21 @@
                     }
                 }
                 if (hresult != 0 || vresult != 0) {
-                    // dummy values, View's implementation does not use these.
-                    onScrollChanged(0, 0, 0, 0);
-                    if (mScrollListener != null) {
-                        mScrollListener.onScrolled(RecyclerView.this, hresult, vresult);
-                    }
+                    notifyOnScrolled(hresult, vresult);
                 }
 
                 if (!awakenScrollBars()) {
                     invalidate();
                 }
 
-                if (scroller.isFinished() || !fullyConsumedScroll) {
+                final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically()
+                        && vresult == dy;
+                final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally()
+                        && hresult == dx;
+                final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal
+                        || fullyConsumedVertical;
+
+                if (scroller.isFinished() || !fullyConsumedAny) {
                     setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
                 } else {
                     postOnAnimation();
@@ -2824,6 +3252,7 @@
             if (mEatRunOnAnimationRequest) {
                 mReSchedulePostAnimationCallback = true;
             } else {
+                removeCallbacks(this);
                 ViewCompat.postOnAnimation(RecyclerView.this, this);
             }
         }
@@ -2894,6 +3323,14 @@
 
     }
 
+    private void notifyOnScrolled(int hresult, int vresult) {
+        // dummy values, View's implementation does not use these.
+        onScrollChanged(0, 0, 0, 0);
+        if (mScrollListener != null) {
+            mScrollListener.onScrolled(this, hresult, vresult);
+        }
+    }
+
     private class RecyclerViewDataObserver extends AdapterDataObserver {
         @Override
         public void onChanged() {
@@ -2903,10 +3340,10 @@
                 // This is more important to implement now since this callback will disable all
                 // animations because we cannot rely on positions.
                 mState.mStructureChanged = true;
-                mDataSetHasChangedAfterLayout = true;
+                setDataSetChangedAfterLayout();
             } else {
                 mState.mStructureChanged = true;
-                mDataSetHasChangedAfterLayout = true;
+                setDataSetChangedAfterLayout();
             }
             if (!mAdapterHelper.hasPendingUpdates()) {
                 requestLayout();
@@ -3112,11 +3549,7 @@
             mViewCacheMax = viewCount;
             // first, try the views that can be recycled
             for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) {
-                tryToRecycleCachedViewAt(i);
-            }
-            // if we could not recycle enough of them, remove some.
-            while (mCachedViews.size() > viewCount) {
-                mCachedViews.remove(mCachedViews.size() - 1);
+                recycleCachedViewAt(i);
             }
         }
 
@@ -3188,6 +3621,7 @@
                         + "position " + position + "(offset:" + offsetPosition + ")."
                         + "state:" + mState.getItemCount());
             }
+            holder.mOwnerRecyclerView = RecyclerView.this;
             mAdapter.bindViewHolder(holder, offsetPosition);
             attachAccessibilityDelegate(view);
             if (mState.isPreLayout()) {
@@ -3336,8 +3770,7 @@
                         Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                 + "pool");
                     }
-                    holder = getRecycledViewPool()
-                            .getRecycledView(mAdapter.getItemViewType(offsetPosition));
+                    holder = getRecycledViewPool().getRecycledView(type);
                     if (holder != null) {
                         holder.resetInternal();
                         if (FORCE_INVALIDATE_DISPLAY_LIST) {
@@ -3346,8 +3779,7 @@
                     }
                 }
                 if (holder == null) {
-                    holder = mAdapter.createViewHolder(RecyclerView.this,
-                            mAdapter.getItemViewType(offsetPosition));
+                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                     if (DEBUG) {
                         Log.d(TAG, "getViewForPosition created new ViewHolder");
                     }
@@ -3363,6 +3795,7 @@
                             + " come here only in pre-layout. Holder: " + holder);
                 }
                 final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+                holder.mOwnerRecyclerView = RecyclerView.this;
                 mAdapter.bindViewHolder(holder, offsetPosition);
                 attachAccessibilityDelegate(holder.itemView);
                 bound = true;
@@ -3432,8 +3865,8 @@
          * Recycle a detached view. The specified view will be added to a pool of views
          * for later rebinding and reuse.
          *
-         * <p>A view must be fully detached before it may be recycled. If the View is scrapped,
-         * it will be removed from scrap list.</p>
+         * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
+         * View is scrapped, it will be removed from scrap list.</p>
          *
          * @param view Removed view for recycling
          * @see LayoutManager#removeAndRecycleView(View, Recycler)
@@ -3442,6 +3875,9 @@
             // This public recycle method tries to make view recycle-able since layout manager
             // intended to recycle this view (e.g. even if it is in scrap or change cache)
             ViewHolder holder = getChildViewHolderInt(view);
+            if (holder.isTmpDetached()) {
+                removeDetachedView(view, false);
+            }
             if (holder.isScrap()) {
                 holder.unScrap();
             } else if (holder.wasReturnedFromScrap()){
@@ -3462,33 +3898,32 @@
         void recycleAndClearCachedViews() {
             final int count = mCachedViews.size();
             for (int i = count - 1; i >= 0; i--) {
-                tryToRecycleCachedViewAt(i);
+                recycleCachedViewAt(i);
             }
             mCachedViews.clear();
         }
 
         /**
-         * Tries to recyle a cached view and removes the view from the list if and only if it
-         * is recycled.
+         * Recycles a cached view and removes the view from the list. Views are added to cache
+         * if and only if they are recyclable, so this method does not check it again.
+         * <p>
+         * A small exception to this rule is when the view does not have an animator reference
+         * but transient state is true (due to animations created outside ItemAnimator). In that
+         * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is
+         * still recyclable since Adapter wants to do so.
          *
          * @param cachedViewIndex The index of the view in cached views list
-         * @return True if item is recycled
          */
-        boolean tryToRecycleCachedViewAt(int cachedViewIndex) {
+        void recycleCachedViewAt(int cachedViewIndex) {
             if (DEBUG) {
                 Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
             }
             ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
             if (DEBUG) {
-                Log.d(TAG, "CachedViewHolder to be recycled(if recycleable): " + viewHolder);
+                Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
             }
-            if (viewHolder.isRecyclable()) {
-                getRecycledViewPool().putRecycledView(viewHolder);
-                dispatchViewRecycled(viewHolder);
-                mCachedViews.remove(cachedViewIndex);
-                return true;
-            }
-            return false;
+            addViewHolderToRecycledViewPool(viewHolder);
+            mCachedViews.remove(cachedViewIndex);
         }
 
         /**
@@ -3504,38 +3939,57 @@
                                 + (holder.itemView.getParent() != null));
             }
 
+            if (holder.isTmpDetached()) {
+                throw new IllegalArgumentException("Tmp detached view should be removed "
+                        + "from RecyclerView before it can be recycled: " + holder);
+            }
+
             if (holder.shouldIgnore()) {
                 throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                         + " should first call stopIgnoringView(view) before calling recycle.");
             }
-            if (holder.isRecyclable()) {
-                boolean cached = false;
+            //noinspection unchecked
+            final boolean transientStatePreventsRecycling = holder
+                    .doesTransientStatePreventRecycling();
+            final boolean forceRecycle = mAdapter != null
+                    && transientStatePreventsRecycling
+                    && mAdapter.onFailedToRecycleView(holder);
+            boolean cached = false;
+            boolean recycled = false;
+            if (forceRecycle || holder.isRecyclable()) {
                 if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
                         !holder.isChanged()) {
-                    // Retire oldest cached views first
-                    if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
-                        for (int i = 0; i < mCachedViews.size(); i++) {
-                            if (tryToRecycleCachedViewAt(i)) {
-                                break;
-                            }
-                        }
+                    // Retire oldest cached view
+                    final int cachedViewSize = mCachedViews.size();
+                    if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
+                        recycleCachedViewAt(0);
                     }
-                    if (mCachedViews.size() < mViewCacheMax) {
+                    if (cachedViewSize < mViewCacheMax) {
                         mCachedViews.add(holder);
                         cached = true;
                     }
                 }
                 if (!cached) {
-                    getRecycledViewPool().putRecycledView(holder);
-                    dispatchViewRecycled(holder);
+                    addViewHolderToRecycledViewPool(holder);
+                    recycled = true;
                 }
             } else if (DEBUG) {
                 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
-                        + "re-visit here. We are stil removing it from animation lists");
+                        + "re-visit here. We are still removing it from animation lists");
             }
             // even if the holder is not removed, we still call this method so that it is removed
             // from view holder lists.
             mState.onViewRecycled(holder);
+            if (!cached && !recycled && transientStatePreventsRecycling) {
+                holder.mOwnerRecyclerView = null;
+            }
+        }
+
+        void addViewHolderToRecycledViewPool(ViewHolder holder) {
+            ViewCompat.setAccessibilityDelegate(holder.itemView, null);
+            dispatchViewRecycled(holder);
+            holder.mOwnerRecyclerView = null;
+            getRecycledViewPool().putRecycledView(holder);
         }
 
         /**
@@ -3614,7 +4068,7 @@
             // find by position
             for (int i = 0; i < changedScrapSize; i++) {
                 final ViewHolder holder = mChangedScrap.get(i);
-                if (!holder.wasReturnedFromScrap() && holder.getPosition() == position) {
+                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                     holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                     return holder;
                 }
@@ -3651,7 +4105,7 @@
             // Try first for an exact, non-invalid match from scrap.
             for (int i = 0; i < scrapCount; i++) {
                 final ViewHolder holder = mAttachedScrap.get(i);
-                if (!holder.wasReturnedFromScrap() && holder.getPosition() == position
+                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                         && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                     if (type != INVALID_TYPE && holder.getItemViewType() != type) {
                         Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
@@ -3678,7 +4132,7 @@
                 final ViewHolder holder = mCachedViews.get(i);
                 // invalid view holders may be in cache if adapter has stable ids as they can be
                 // retrieved via getScrapViewForId
-                if (!holder.isInvalid() && holder.getPosition() == position) {
+                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
                     if (!dryRun) {
                         mCachedViews.remove(i);
                     }
@@ -3735,7 +4189,7 @@
                         }
                         return holder;
                     } else if (!dryRun) {
-                        tryToRecycleCachedViewAt(i);
+                        recycleCachedViewAt(i);
                     }
                 }
             }
@@ -3794,7 +4248,7 @@
             final int cachedCount = mCachedViews.size();
             for (int i = 0; i < cachedCount; i++) {
                 final ViewHolder holder = mCachedViews.get(i);
-                if (holder != null && holder.getPosition() >= insertedAt) {
+                if (holder != null && holder.getLayoutPosition() >= insertedAt) {
                     if (DEBUG) {
                         Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
                                 holder + " now at position " + (holder.mPosition + count));
@@ -3816,28 +4270,17 @@
             for (int i = cachedCount - 1; i >= 0; i--) {
                 final ViewHolder holder = mCachedViews.get(i);
                 if (holder != null) {
-                    if (holder.getPosition() >= removedEnd) {
+                    if (holder.getLayoutPosition() >= removedEnd) {
                         if (DEBUG) {
                             Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
                                     " holder " + holder + " now at position " +
                                     (holder.mPosition - count));
                         }
                         holder.offsetPosition(-count, applyToPreLayout);
-                    } else if (holder.getPosition() >= removedFrom) {
+                    } else if (holder.getLayoutPosition() >= removedFrom) {
                         // Item for this view was removed. Dump it from the cache.
-                        if (!tryToRecycleCachedViewAt(i)) {
-                            // if we cannot recycle it, at least invalidate so that we won't return
-                            // it by position.
-                            holder.addFlags(ViewHolder.FLAG_INVALID);
-                            if (DEBUG) {
-                                Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
-                                        " holder " + holder + " now flagged as invalid because it "
-                                        + "could not be recycled");
-                            }
-                        } else if (DEBUG) {
-                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
-                                    " holder " + holder + " now placed in pool");
-                        }
+                        holder.addFlags(ViewHolder.FLAG_REMOVED);
+                        recycleCachedViewAt(i);
                     }
                 }
             }
@@ -3873,7 +4316,7 @@
                     continue;
                 }
 
-                final int pos = holder.getPosition();
+                final int pos = holder.getLayoutPosition();
                 if (pos >= positionStart && pos < positionEnd) {
                     holder.addFlags(ViewHolder.FLAG_UPDATE);
                     // cached views should not be flagged as changed because this will cause them
@@ -3882,6 +4325,16 @@
             }
         }
 
+        void setAdapterPositionsAsUnknown() {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null) {
+                    holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+                }
+            }
+        }
+
         void markKnownViewsInvalid() {
             if (mAdapter != null && mAdapter.hasStableIds()) {
                 final int cachedCount = mCachedViews.size();
@@ -3892,17 +4345,9 @@
                     }
                 }
             } else {
-                // we cannot re-use cached views in this case. Recycle the ones we can and flag
-                // the remaining as invalid so that they can be recycled later on (when their
-                // animations end.)
-                for (int i = mCachedViews.size() - 1; i >= 0; i--) {
-                    if (!tryToRecycleCachedViewAt(i)) {
-                        final ViewHolder holder = mCachedViews.get(i);
-                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
-                    }
-                }
+                // we cannot re-use cached views in this case. Recycle them all
+                recycleAndClearCachedViews();
             }
-
         }
 
         void clearOldPositions() {
@@ -4012,8 +4457,8 @@
          * is invalidated or the new position cannot be determined. For this reason, you should only
          * use the <code>position</code> parameter while acquiring the related data item inside this
          * method and should not keep a copy of it. If you need the position of an item later on
-         * (e.g. in a click listener), use {@link ViewHolder#getPosition()} which will have the
-         * updated position.
+         * (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will have
+         * the updated adapter position.
          *
          * @param holder The ViewHolder which should be updated to represent the contents of the
          *               item at the given position in the data set.
@@ -4045,9 +4490,10 @@
             if (hasStableIds()) {
                 holder.mItemId = getItemId(position);
             }
-            onBindViewHolder(holder, position);
             holder.setFlags(ViewHolder.FLAG_BOUND,
-                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
+                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
+            onBindViewHolder(holder, position);
         }
 
         /**
@@ -4121,6 +4567,11 @@
          * attached to the parent RecyclerView. If an item view has large or expensive data
          * bound to it such as large bitmaps, this may be a good place to release those
          * resources.</p>
+         * <p>
+         * RecyclerView calls this method right before clearing ViewHolder's internal data and
+         * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+         * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+         * its adapter position.
          *
          * @param holder The ViewHolder for the view being recycled
          */
@@ -4128,6 +4579,44 @@
         }
 
         /**
+         * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled
+         * due to its transient state. Upon receiving this callback, Adapter can clear the
+         * animation(s) that effect the View's transient state and return <code>true</code> so that
+         * the View can be recycled. Keep in mind that the View in question is already removed from
+         * the RecyclerView.
+         * <p>
+         * In some cases, it is acceptable to recycle a View although it has transient state. Most
+         * of the time, this is a case where the transient state will be cleared in
+         * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position.
+         * For this reason, RecyclerView leaves the decision to the Adapter and uses the return
+         * value of this method to decide whether the View should be recycled or not.
+         * <p>
+         * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you
+         * should never receive this callback because RecyclerView keeps those Views as children
+         * until their animations are complete. This callback is useful when children of the item
+         * views create animations which may not be easy to implement using an {@link ItemAnimator}.
+         * <p>
+         * You should <em>never</em> fix this issue by calling
+         * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called
+         * <code>holder.itemView.setHasTransientState(true);</code>. Each
+         * <code>View.setHasTransientState(true)</code> call must be matched by a
+         * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View
+         * may become inconsistent. You should always prefer to end or cancel animations that are
+         * triggering the transient state instead of handling it manually.
+         *
+         * @param holder The ViewHolder containing the View that could not be recycled due to its
+         *               transient state.
+         * @return True if the View should be recycled, false otherwise. Note that if this method
+         * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of
+         * the View and recycle it regardless. If this method returns <code>false</code>,
+         * RecyclerView will check the View's transient state again before giving a final decision.
+         * Default implementation returns false.
+         */
+        public boolean onFailedToRecycleView(VH holder) {
+            return false;
+        }
+
+        /**
          * Called when a view created by this adapter has been attached to a window.
          *
          * <p>This can be used as a reasonable signal that the view is about to be seen
@@ -4196,6 +4685,26 @@
         }
 
         /**
+         * Called by RecyclerView when it starts observing this Adapter.
+         * <p>
+         * Keep in mind that same adapter may be observed by multiple RecyclerViews.
+         *
+         * @param recyclerView The RecyclerView instance which started observing this adapter.
+         * @see #onDetachedFromRecyclerView(RecyclerView)
+         */
+        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        }
+
+        /**
+         * Called by RecyclerView when it stops observing this Adapter.
+         *
+         * @param recyclerView The RecyclerView instance which stopped observing this adapter.
+         * @see #onAttachedToRecyclerView(RecyclerView)
+         */
+        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+        }
+
+        /**
          * Notify any registered observers that the data set has changed.
          *
          * <p>There are two different classes of data change events, item changes and structural
@@ -4892,19 +5401,19 @@
             // Only remove non-animating views
             final int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; i--) {
-                final View child = getChildAt(i);
                 mChildHelper.removeViewAt(i);
             }
         }
 
         /**
-         * Returns the adapter position of the item represented by the given View.
+         * Returns the adapter position of the item represented by the given View. This does not
+         * contain any adapter changes that might have happened after the last layout.
          *
          * @param view The view to query
          * @return The adapter position of the item which is rendered by this View.
          */
         public int getPosition(View view) {
-            return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition();
+            return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
         }
 
         /**
@@ -4918,7 +5427,6 @@
         }
 
         /**
-         * <p>
          * Finds the view which represents the given adapter position.
          * <p>
          * This method traverses each child since it has no information about child order.
@@ -4929,7 +5437,7 @@
          *
          * @param position Position of the item in adapter
          * @return The child view that represents the given position or null if the position is not
-         * visible
+         * laid out
          */
         public View findViewByPosition(int position) {
             final int childCount = getChildCount();
@@ -4939,7 +5447,7 @@
                 if (vh == null) {
                     continue;
                 }
-                if (vh.getPosition() == position && !vh.shouldIgnore() &&
+                if (vh.getLayoutPosition() == position && !vh.shouldIgnore() &&
                         (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
                     return child;
                 }
@@ -5367,23 +5875,22 @@
          * call remove and invalidate RecyclerView to ensure UI update.
          *
          * @param recycler Recycler
-         * @param remove   Whether scrapped views should be removed from ViewGroup or not. This
-         *                 method will invalidate RecyclerView if it removes any scrapped child.
          */
-        void removeAndRecycleScrapInt(Recycler recycler, boolean remove) {
+        void removeAndRecycleScrapInt(Recycler recycler) {
             final int scrapCount = recycler.getScrapCount();
             for (int i = 0; i < scrapCount; i++) {
                 final View scrap = recycler.getScrapViewAt(i);
-                if (getChildViewHolderInt(scrap).shouldIgnore()) {
+                final ViewHolder vh = getChildViewHolderInt(scrap);
+                if (vh.shouldIgnore()) {
                     continue;
                 }
-                if (remove) {
+                if (vh.isTmpDetached()) {
                     mRecyclerView.removeDetachedView(scrap, false);
                 }
                 recycler.quickRecycleScrapView(scrap);
             }
             recycler.clearScrap();
-            if (remove && scrapCount > 0) {
+            if (scrapCount > 0) {
                 mRecyclerView.invalidate();
             }
         }
@@ -5748,8 +6255,8 @@
             final int parentBottom = getHeight() - getPaddingBottom();
             final int childLeft = child.getLeft() + rect.left;
             final int childTop = child.getTop() + rect.top;
-            final int childRight = childLeft + rect.right;
-            final int childBottom = childTop + rect.bottom;
+            final int childRight = childLeft + rect.width();
+            final int childBottom = childTop + rect.height();
 
             final int offScreenLeft = Math.min(0, childLeft - parentLeft);
             final int offScreenTop = Math.min(0, childTop - parentTop);
@@ -5784,7 +6291,8 @@
          */
         @Deprecated
         public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
-            return false;
+            // eat the request if we are in the middle of a scroll or layout
+            return isSmoothScrolling() || parent.mRunningLayoutOrScroll;
         }
 
         /**
@@ -6016,37 +6524,7 @@
          * @param heightSpec Height {@link android.view.View.MeasureSpec}
          */
         public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
-            final int widthMode = MeasureSpec.getMode(widthSpec);
-            final int heightMode = MeasureSpec.getMode(heightSpec);
-            final int widthSize = MeasureSpec.getSize(widthSpec);
-            final int heightSize = MeasureSpec.getSize(heightSpec);
-
-            int width = 0;
-            int height = 0;
-
-            switch (widthMode) {
-                case MeasureSpec.EXACTLY:
-                case MeasureSpec.AT_MOST:
-                    width = widthSize;
-                    break;
-                case MeasureSpec.UNSPECIFIED:
-                default:
-                    width = getMinimumWidth();
-                    break;
-            }
-
-            switch (heightMode) {
-                case MeasureSpec.EXACTLY:
-                case MeasureSpec.AT_MOST:
-                    height = heightSize;
-                    break;
-                case MeasureSpec.UNSPECIFIED:
-                default:
-                    height = getMinimumHeight();
-                    break;
-            }
-
-            setMeasuredDimension(width, height);
+            mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
         }
 
         /**
@@ -6220,10 +6698,13 @@
         }
 
         // called by accessibility delegate
-        void onInitializeAccessibilityNodeInfoForItem(View host,
-                AccessibilityNodeInfoCompat info) {
-            onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, mRecyclerView.mState,
-                    host, info);
+        void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfoCompat info) {
+            final ViewHolder vh = getChildViewHolderInt(host);
+            // avoid trying to create accessibility node info for removed children
+            if (vh != null && !vh.isRemoved()) {
+                onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler,
+                        mRecyclerView.mState, host, info);
+            }
         }
 
         /**
@@ -6390,7 +6871,7 @@
 
         /**
          * Called by AccessibilityDelegate when an accessibility action is requested on one of the
-         * chidren of LayoutManager.
+         * children of LayoutManager.
          * <p>
          * Default implementation does not do anything.
          *
@@ -6487,9 +6968,15 @@
          * the number of pixels that the item view should be inset by, similar to padding or margin.
          * The default implementation sets the bounds of outRect to 0 and returns.
          *
-         * <p>If this ItemDecoration does not affect the positioning of item views it should set
+         * <p>
+         * If this ItemDecoration does not affect the positioning of item views, it should set
          * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
-         * before returning.</p>
+         * before returning.
+         *
+         * <p>
+         * If you need to access Adapter for additional data, you can call
+         * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
+         * View.
          *
          * @param outRect Rect to receive the output.
          * @param view    The child view to decorate
@@ -6497,7 +6984,7 @@
          * @param state   The current state of RecyclerView.
          */
         public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
-            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewPosition(),
+            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                     parent);
         }
     }
@@ -6558,6 +7045,9 @@
         /**
          * Callback method to be invoked when the RecyclerView has been scrolled. This will be
          * called after the scroll has completed.
+         * <p>
+         * This callback will also be called if visible item range changes after a layout
+         * calculation. In that case, dx and dy will be 0.
          *
          * @param recyclerView The RecyclerView which scrolled.
          * @param dx The amount of horizontal scroll.
@@ -6577,6 +7067,11 @@
         /**
          * This method is called whenever the view in the ViewHolder is recycled.
          *
+         * RecyclerView calls this method right before clearing ViewHolder's internal data and
+         * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information
+         * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get
+         * its adapter position.
+         *
          * @param holder The ViewHolder containing the view that was recycled
          */
         public void onViewRecycled(ViewHolder holder);
@@ -6660,6 +7155,21 @@
          */
         static final int FLAG_IGNORE = 1 << 7;
 
+        /**
+         * When the View is detached form the parent, we set this flag so that we can take correct
+         * action when we need to remove it or add it back.
+         */
+        static final int FLAG_TMP_DETACHED = 1 << 8;
+
+        /**
+         * Set when we can no longer determine the adapter position of this ViewHolder until it is
+         * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
+         * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
+         * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
+         * re-calculated.
+         */
+        static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;
+
         private int mFlags;
 
         private int mIsRecyclableCount = 0;
@@ -6668,6 +7178,12 @@
         // scrap container.
         private Recycler mScrapContainer = null;
 
+        /**
+         * Is set when VH is bound from the adapter and cleaned right before it is sent to
+         * {@link RecycledViewPool}.
+         */
+        RecyclerView mOwnerRecyclerView;
+
         public ViewHolder(View itemView) {
             if (itemView == null) {
                 throw new IllegalArgumentException("itemView may not be null");
@@ -6712,11 +7228,75 @@
             return (mFlags & FLAG_IGNORE) != 0;
         }
 
+        /**
+         * @deprecated This method is deprecated because its meaning is ambiguous due to the async
+         * handling of adapter updates. Please use {@link #getLayoutPosition()} or
+         * {@link #getAdapterPosition()} depending on your use case.
+         *
+         * @see #getLayoutPosition()
+         * @see #getAdapterPosition()
+         */
+        @Deprecated
         public final int getPosition() {
             return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
         }
 
         /**
+         * Returns the position of the ViewHolder in terms of the latest layout pass.
+         * <p>
+         * This position is mostly used by RecyclerView components to be consistent while
+         * RecyclerView lazily processes adapter updates.
+         * <p>
+         * For performance and animation reasons, RecyclerView batches all adapter updates until the
+         * next layout pass. This may cause mismatches between the Adapter position of the item and
+         * the position it had in the latest layout calculations.
+         * <p>
+         * LayoutManagers should always call this method while doing calculations based on item
+         * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
+         * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
+         * of the item.
+         * <p>
+         * If LayoutManager needs to call an external method that requires the adapter position of
+         * the item, it can use {@link #getAdapterPosition()} or
+         * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
+         *
+         * @return Returns the adapter position of the ViewHolder in the latest layout pass.
+         * @see #getAdapterPosition()
+         */
+        public final int getLayoutPosition() {
+            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+        }
+
+        /**
+         * Returns the Adapter position of the item represented by this ViewHolder.
+         * <p>
+         * Note that this might be different than the {@link #getLayoutPosition()} if there are
+         * pending adapter updates but a new layout pass has not happened yet.
+         * <p>
+         * RecyclerView does not handle any adapter updates until the next layout traversal. This
+         * may create temporary inconsistencies between what user sees on the screen and what
+         * adapter contents have. This inconsistency is not important since it will be less than
+         * 16ms but it might be a problem if you want to use ViewHolder position to access the
+         * adapter. Sometimes, you may need to get the exact adapter position to do
+         * some actions in response to user events. In that case, you should use this method which
+         * will calculate the Adapter position of the ViewHolder.
+         * <p>
+         * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
+         * next layout pass, the return value of this method will be {@link #NO_POSITION}.
+         *
+         * @return The adapter position of the item if it still exists in the adapter.
+         * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
+         * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
+         * layout pass or the ViewHolder has already been recycled.
+         */
+        public final int getAdapterPosition() {
+            if (mOwnerRecyclerView == null) {
+                return NO_POSITION;
+            }
+            return mOwnerRecyclerView.getAdapterPositionFor(this);
+        }
+
+        /**
          * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
          * to perform animations.
          * <p>
@@ -6764,6 +7344,10 @@
             mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
         }
 
+        void clearTmpDetachFlag() {
+            mFlags = mFlags & ~FLAG_TMP_DETACHED;
+        }
+
         void stopIgnoring() {
             mFlags = mFlags & ~FLAG_IGNORE;
         }
@@ -6792,6 +7376,18 @@
             return (mFlags & FLAG_REMOVED) != 0;
         }
 
+        boolean hasAnyOfTheFlags(int flags) {
+            return (mFlags & flags) != 0;
+        }
+
+        boolean isTmpDetached() {
+            return (mFlags & FLAG_TMP_DETACHED) != 0;
+        }
+
+        boolean isAdapterPositionUnknown() {
+            return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid();
+        }
+
         void setFlags(int flags, int mask) {
             mFlags = (mFlags & ~mask) | (flags & mask);
         }
@@ -6823,7 +7419,10 @@
             if (isRemoved()) sb.append(" removed");
             if (shouldIgnore()) sb.append(" ignored");
             if (isChanged()) sb.append(" changed");
+            if (isTmpDetached()) sb.append(" tmpDetached");
             if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
+            if (isAdapterPositionUnknown()) sb.append("undefined adapter position");
+
             if (itemView.getParent() == null) sb.append(" no parent");
             sb.append("}");
             return sb.toString();
@@ -6869,6 +7468,31 @@
             return (mFlags & FLAG_NOT_RECYCLABLE) == 0 &&
                     !ViewCompat.hasTransientState(itemView);
         }
+
+        /**
+         * Returns whether we have animations referring to this view holder or not.
+         * This is similar to isRecyclable flag but does not check transient state.
+         */
+        private boolean shouldBeKeptAsChild() {
+            return (mFlags & FLAG_NOT_RECYCLABLE) != 0;
+        }
+
+        /**
+         * @return True if ViewHolder is not refenrenced by RecyclerView animations but has
+         * transient state which will prevent it from being recycled.
+         */
+        private boolean doesTransientStatePreventRecycling() {
+            return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView);
+        }
+    }
+
+    private int getAdapterPositionFor(ViewHolder viewHolder) {
+        if (viewHolder.hasAnyOfTheFlags( ViewHolder.FLAG_INVALID |
+                ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
+                || !viewHolder.isBound()) {
+            return RecyclerView.NO_POSITION;
+        }
+        return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
     }
 
     /**
@@ -6949,13 +7573,33 @@
         }
 
         /**
-         * Returns the position that the view this LayoutParams is attached to corresponds to.
-         *
-         * @return the adapter position this view was bound from
+         * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
          */
         public int getViewPosition() {
             return mViewHolder.getPosition();
         }
+
+        /**
+         * Returns the adapter position that the view this LayoutParams is attached to corresponds
+         * to as of latest layout calculation.
+         *
+         * @return the adapter position this view as of latest layout pass
+         */
+        public int getViewLayoutPosition() {
+            return mViewHolder.getLayoutPosition();
+        }
+
+        /**
+         * Returns the up-to-date adapter position that the view this LayoutParams is attached to
+         * corresponds to.
+         *
+         * @return the up-to-date adapter position this view. It may return
+         * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or
+         * its up-to-date position cannot be calculated.
+         */
+        public int getViewAdapterPosition() {
+            return mViewHolder.getAdapterPosition();
+        }
     }
 
     /**
@@ -7122,10 +7766,10 @@
         }
 
         /**
-         * @see RecyclerView#getChildPosition(android.view.View)
+         * @see RecyclerView#getChildLayoutPosition(android.view.View)
          */
         public int getChildPosition(View view) {
-            return mRecyclerView.getChildPosition(view);
+            return mRecyclerView.getChildLayoutPosition(view);
         }
 
         /**
@@ -7202,7 +7846,6 @@
          * @param state         Transient state of RecyclerView
          * @param action        Action instance that you should update to define final scroll action
          *                      towards the targetView
-         * @return An {@link Action} to finalize the smooth scrolling
          */
         abstract protected void onTargetFound(View targetView, State state, Action action);
 
@@ -7627,7 +8270,7 @@
                     mItemCount;
         }
 
-        public void onViewRecycled(ViewHolder holder) {
+        void onViewRecycled(ViewHolder holder) {
             mPreLayoutHolderMap.remove(holder);
             mPostLayoutHolderMap.remove(holder);
             if (mOldChangedHolders != null) {
@@ -7679,14 +8322,15 @@
         @Override
         public void onRemoveFinished(ViewHolder item) {
             item.setIsRecyclable(true);
-            removeAnimatingView(item.itemView);
-            removeDetachedView(item.itemView, false);
+            if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
+                removeDetachedView(item.itemView, false);
+            }
         }
 
         @Override
         public void onAddFinished(ViewHolder item) {
             item.setIsRecyclable(true);
-            if (item.isRecyclable()) {
+            if (!item.shouldBeKeptAsChild()) {
                 removeAnimatingView(item.itemView);
             }
         }
@@ -7694,7 +8338,7 @@
         @Override
         public void onMoveFinished(ViewHolder item) {
             item.setIsRecyclable(true);
-            if (item.isRecyclable()) {
+            if (!item.shouldBeKeptAsChild()) {
                 removeAnimatingView(item.itemView);
             }
         }
@@ -7736,7 +8380,7 @@
             // always null this because an OldViewHolder can never become NewViewHolder w/o being
             // recycled.
             item.mShadowingHolder = null;
-            if (item.isRecyclable()) {
+            if (!item.shouldBeKeptAsChild()) {
                 removeAnimatingView(item.itemView);
             }
         }
@@ -7773,7 +8417,7 @@
         private long mMoveDuration = 250;
         private long mChangeDuration = 250;
 
-        private boolean mSupportsChangeAnimations = false;
+        private boolean mSupportsChangeAnimations = true;
 
         /**
          * Gets the current duration for which all move animations will run.
@@ -7858,12 +8502,11 @@
 
         /**
          * Sets whether this ItemAnimator supports animations of item change events.
-         * By default, ItemAnimator only supports animations when items are added or removed.
-         * By setting this property to true, actions on the data set which change the
-         * contents of items may also be animated. What those animations are is left
+         * If you set this property to false, actions on the data set which change the
+         * contents of items will not be animated. What those animations are is left
          * up to the discretion of the ItemAnimator subclass, in its
          * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
-         * The value of this property is false by default.
+         * The value of this property is true by default.
          *
          * @see Adapter#notifyItemChanged(int)
          * @see Adapter#notifyItemRangeChanged(int, int)
diff --git a/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
index 0903f64..724fac8a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
@@ -33,17 +33,20 @@
                 endChild == null) {
             return 0;
         }
-        final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild));
-        final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild));
+        final int minPosition = Math.min(lm.getPosition(startChild),
+                lm.getPosition(endChild));
+        final int maxPosition = Math.max(lm.getPosition(startChild),
+                lm.getPosition(endChild));
         final int itemsBefore = reverseLayout
                 ? Math.max(0, state.getItemCount() - maxPosition - 1)
-                : Math.max(0, minPosition - 1);
+                : Math.max(0, minPosition);
         if (!smoothScrollbarEnabled) {
             return itemsBefore;
         }
         final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
                 orientation.getDecoratedStart(startChild));
-        final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+        final int itemRange = Math.abs(lm.getPosition(startChild) -
+                lm.getPosition(endChild)) + 1;
         final float avgSizePerRow = (float) laidOutArea / itemRange;
 
         return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
@@ -86,7 +89,8 @@
         // smooth scrollbar enabled. try to estimate better.
         final int laidOutArea = orientation.getDecoratedEnd(endChild) -
                 orientation.getDecoratedStart(startChild);
-        final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild))
+        final int laidOutRange = Math.abs(lm.getPosition(startChild) -
+                lm.getPosition(endChild))
                 + 1;
         // estimate a size for full list.
         return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index aab16e4..0abed16 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -74,17 +74,18 @@
      * and move items to correct positions with animations.
      * <p>
      * For example, if LayoutManager ends up with the following layout due to adapter changes:
-     * <code>
+     * <pre>
      * AAA
      * _BC
      * DDD
-     * </code>
+     * </pre>
+     * <p>
      * It will animate to the following state:
-     * <code>
+     * <pre>
      * AAA
      * BC_
      * DDD
-     * </code>
+     * </pre>
      */
     public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
 
@@ -239,7 +240,7 @@
         }
         int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
         final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
-                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir);
+                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
         if (invalidFsi == null) {
             mLaidOutInvalidFullSpan = false;
             mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
@@ -247,7 +248,7 @@
         }
         final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
                 .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
-                        invalidGapDir * -1);
+                        invalidGapDir * -1, true);
         if (validFsi == null) {
             mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
         } else {
@@ -530,8 +531,6 @@
     @Override
     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
         ensureOrientationHelper();
-        // Update adapter size.
-        mLazySpanLookup.mAdapterSize = state.getItemCount();
 
         final AnchorInfo anchorInfo = mAnchorInfo;
         anchorInfo.reset();
@@ -913,9 +912,10 @@
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureOrientationHelper();
         return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
-                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
-                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                 this, mSmoothScrollbarEnabled, mShouldReverseLayout);
     }
 
@@ -933,9 +933,10 @@
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureOrientationHelper();
         return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
-                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
-                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                 this, mSmoothScrollbarEnabled);
     }
 
@@ -953,9 +954,10 @@
         if (getChildCount() == 0) {
             return 0;
         }
+        ensureOrientationHelper();
         return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
-                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
-                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
                 this, mSmoothScrollbarEnabled);
     }
 
@@ -967,12 +969,28 @@
     private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
         if (lp.mFullSpan) {
             if (mOrientation == VERTICAL) {
-                measureChildWithDecorationsAndMargin(child, mFullSizeSpec, mHeightSpec);
+                measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
+                        getSpecForDimension(lp.height, mHeightSpec));
             } else {
-                measureChildWithDecorationsAndMargin(child, mWidthSpec, mFullSizeSpec);
+                measureChildWithDecorationsAndMargin(child,
+                        getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
             }
         } else {
-            measureChildWithDecorationsAndMargin(child, mWidthSpec, mHeightSpec);
+            if (mOrientation == VERTICAL) {
+                measureChildWithDecorationsAndMargin(child, mWidthSpec,
+                        getSpecForDimension(lp.height, mHeightSpec));
+            } else {
+                measureChildWithDecorationsAndMargin(child,
+                        getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
+            }
+        }
+    }
+
+    private int getSpecForDimension(int dim, int defaultSpec) {
+        if (dim < 0) {
+            return defaultSpec;
+        } else {
+            return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
         }
     }
 
@@ -1028,6 +1046,7 @@
         }
 
         if (getChildCount() > 0) {
+            ensureOrientationHelper();
             state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
                     : getFirstChildPosition();
             state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
@@ -1087,8 +1106,8 @@
         if (getChildCount() > 0) {
             final AccessibilityRecordCompat record = AccessibilityEventCompat
                     .asRecord(event);
-            final View start = findFirstVisibleItemClosestToStart(false);
-            final View end = findFirstVisibleItemClosestToEnd(false);
+            final View start = findFirstVisibleItemClosestToStart(false, true);
+            final View end = findFirstVisibleItemClosestToEnd(false, true);
             if (start == null || end == null) {
                 return;
             }
@@ -1106,11 +1125,12 @@
 
     /**
      * Finds the first fully visible child to be used as an anchor child if span count changes when
-     * state is restored.
+     * state is restored. If no children is fully visible, returns a partially visible child instead
+     * of returning null.
      */
     int findFirstVisibleItemPositionInt() {
-        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
-                findFirstVisibleItemClosestToStart(true);
+        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) :
+                findFirstVisibleItemClosestToStart(true, true);
         return first == null ? NO_POSITION : getPosition(first);
     }
 
@@ -1132,31 +1152,42 @@
         return super.getColumnCountForAccessibility(recycler, state);
     }
 
-    View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
+    View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) {
+        ensureOrientationHelper();
         final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
         final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
         final int limit = getChildCount();
-        for (int i = 0; i < limit; i ++) {
+        View partiallyVisible = null;
+        for (int i = 0; i < limit; i++) {
             final View child = getChildAt(i);
-            if ((!fullyVisible || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)
-                    && mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
-                return child;
+            if (mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
+                if ((!fullyVisible
+                        || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)) {
+                    return child;
+                } else if (acceptPartiallyVisible && partiallyVisible == null) {
+                    partiallyVisible = child;
+                }
             }
         }
-        return null;
+        return partiallyVisible;
     }
 
-    View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
+    View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) {
+        ensureOrientationHelper();
         final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
         final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
-        for (int i = getChildCount() - 1; i >= 0; i --) {
+        View partiallyVisible = null;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
             final View child = getChildAt(i);
-            if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart && (!fullyVisible
-                    || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd)) {
-                return child;
+            if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart) {
+                if (!fullyVisible || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
+                    return child;
+                } else if (acceptPartiallyVisible && partiallyVisible == null) {
+                    partiallyVisible = child;
+                }
             }
         }
-        return null;
+        return partiallyVisible;
     }
 
     private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
@@ -1274,7 +1305,23 @@
      */
     private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
         int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
-        mLazySpanLookup.invalidateAfter(positionStart);
+        final int affectedRangeEnd;// exclusive
+        final int affectedRangeStart;// inclusive
+
+        if (cmd == AdapterHelper.UpdateOp.MOVE) {
+            if (positionStart < itemCountOrToPosition) {
+                affectedRangeEnd = itemCountOrToPosition + 1;
+                affectedRangeStart = positionStart;
+            } else {
+                affectedRangeEnd = positionStart + 1;
+                affectedRangeStart = itemCountOrToPosition;
+            }
+        } else {
+            affectedRangeStart = positionStart;
+            affectedRangeEnd = positionStart + itemCountOrToPosition;
+        }
+
+        mLazySpanLookup.invalidateAfter(affectedRangeStart);
         switch (cmd) {
             case AdapterHelper.UpdateOp.ADD:
                 mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
@@ -1289,12 +1336,12 @@
                 break;
         }
 
-        if (positionStart + itemCountOrToPosition <= minPosition) {
+        if (affectedRangeEnd <= minPosition) {
             return;
-
         }
+
         int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
-        if (positionStart <= maxPosition) {
+        if (affectedRangeStart <= maxPosition) {
             requestLayout();
         }
     }
@@ -1332,17 +1379,10 @@
         while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
             View view = layoutState.next(recycler);
             LayoutParams lp = ((LayoutParams) view.getLayoutParams());
-            if (layoutState.mLayoutDirection == LAYOUT_END) {
-                addView(view);
-            } else {
-                addView(view, 0);
-            }
-            measureChildWithDecorationsAndMargin(view, lp);
-
-            final int position = lp.getViewPosition();
+            final int position = lp.getViewLayoutPosition();
             final int spanIndex = mLazySpanLookup.getSpan(position);
             Span currentSpan;
-            boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
+            final boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
             if (assignSpan) {
                 currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
                 mLazySpanLookup.setSpan(position, currentSpan);
@@ -1355,9 +1395,17 @@
                 }
                 currentSpan = mSpans[spanIndex];
             }
+            // assign span before measuring so that item decorators can get updated span index
+            lp.mSpan = currentSpan;
+            if (layoutState.mLayoutDirection == LAYOUT_END) {
+                addView(view);
+            } else {
+                addView(view, 0);
+            }
+            measureChildWithDecorationsAndMargin(view, lp);
+
             final int start;
             final int end;
-
             if (layoutState.mLayoutDirection == LAYOUT_END) {
                 start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
                         : currentSpan.getEndLine(defaultNewViewLine);
@@ -1383,11 +1431,27 @@
             }
 
             // check if this item may create gaps in the future
-            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD && assignSpan) {
-                mLaidOutInvalidFullSpan = true;
-            }
+            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD) {
+                if (assignSpan) {
+                    mLaidOutInvalidFullSpan = true;
+                } else {
+                    final boolean hasInvalidGap;
+                    if (layoutState.mLayoutDirection == LAYOUT_END) {
+                        hasInvalidGap = !areAllEndsEqual();
+                    } else { // layoutState.mLayoutDirection == LAYOUT_START
+                        hasInvalidGap = !areAllStartsEqual();
+                    }
+                    if (hasInvalidGap) {
+                        final LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
+                                .getFullSpanItem(position);
+                        if (fullSpanItem != null) {
+                            fullSpanItem.mHasUnwantedGapAfter = true;
+                        }
+                        mLaidOutInvalidFullSpan = true;
+                    }
+                }
 
-            lp.mSpan = currentSpan;
+            }
             attachViewToSpans(view, lp, layoutState);
             final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
                     : currentSpan.mIndex * mSizePerSpan +
@@ -1484,7 +1548,7 @@
     private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
         if (DEBUG) {
-            Log.d(TAG, "layout decorated pos: " + lp.getViewPosition() + ", span:"
+            Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:"
                     + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
                     + ". l:" + left + ",t:" + top
                     + ", r:" + right + ", b:" + bottom);
@@ -1539,6 +1603,26 @@
         return minStart;
     }
 
+    boolean areAllEndsEqual() {
+        int end = mSpans[0].getEndLine(Span.INVALID_LINE);
+        for (int i = 1; i < mSpanCount; i++) {
+            if (mSpans[i].getEndLine(Span.INVALID_LINE) != end) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    boolean areAllStartsEqual() {
+        int start = mSpans[0].getStartLine(Span.INVALID_LINE);
+        for (int i = 1; i < mSpanCount; i++) {
+            if (mSpans[i].getStartLine(Span.INVALID_LINE) != start) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private int getMaxEnd(int def) {
         int maxEnd = mSpans[0].getEndLine(def);
         for (int i = 1; i < mSpanCount; i++) {
@@ -1853,6 +1937,10 @@
 
     /**
      * LayoutParams used by StaggeredGridLayoutManager.
+     * <p>
+     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
+     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
+     * expected to fill all of the space given to it.
      */
     public static class LayoutParams extends RecyclerView.LayoutParams {
 
@@ -1953,7 +2041,7 @@
             mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
             if (lp.mFullSpan) {
                 LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
-                        .getFullSpanItem(lp.getViewPosition());
+                        .getFullSpanItem(lp.getViewLayoutPosition());
                 if (fsi != null && fsi.mGapDir == LAYOUT_START) {
                     mCachedStart -= fsi.getGapForSpan(mIndex);
                 }
@@ -1987,7 +2075,7 @@
             mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
             if (lp.mFullSpan) {
                 LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
-                        .getFullSpanItem(lp.getViewPosition());
+                        .getFullSpanItem(lp.getViewLayoutPosition());
                 if (fsi != null && fsi.mGapDir == LAYOUT_END) {
                     mCachedEnd += fsi.getGapForSpan(mIndex);
                 }
@@ -2042,7 +2130,7 @@
                 return;
             }
             if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
-                    (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding()) ) {
+                    (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
                 return;
             }
             if (offset != INVALID_OFFSET) {
@@ -2151,26 +2239,26 @@
 
         public int findFirstVisibleItemPosition() {
             return mReverseLayout
-                    ? findOneVisibleChild(mViews.size() -1, -1, false)
+                    ? findOneVisibleChild(mViews.size() - 1, -1, false)
                     : findOneVisibleChild(0, mViews.size(), false);
         }
 
         public int findFirstCompletelyVisibleItemPosition() {
             return mReverseLayout
-                    ? findOneVisibleChild(mViews.size() -1, -1, true)
+                    ? findOneVisibleChild(mViews.size() - 1, -1, true)
                     : findOneVisibleChild(0, mViews.size(), true);
         }
 
         public int findLastVisibleItemPosition() {
             return mReverseLayout
                     ? findOneVisibleChild(0, mViews.size(), false)
-                    : findOneVisibleChild(mViews.size() -1, -1, false);
+                    : findOneVisibleChild(mViews.size() - 1, -1, false);
         }
 
         public int findLastCompletelyVisibleItemPosition() {
             return mReverseLayout
                     ? findOneVisibleChild(0, mViews.size(), true)
-                    : findOneVisibleChild(mViews.size() -1, -1, true);
+                    : findOneVisibleChild(mViews.size() - 1, -1, true);
         }
 
         int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
@@ -2203,7 +2291,6 @@
 
         private static final int MIN_SIZE = 10;
         int[] mData;
-        int mAdapterSize; // we don't want to grow beyond that, unless it grows
         List<FullSpanItem> mFullSpanItems;
 
 
@@ -2261,9 +2348,6 @@
             while (len <= position) {
                 len *= 2;
             }
-            if (len > mAdapterSize) {
-                len = mAdapterSize;
-            }
             return len;
         }
 
@@ -2411,17 +2495,23 @@
          * @param minPos inclusive
          * @param maxPos exclusive
          * @param gapDir if not 0, returns FSIs on in that direction
+         * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
+         *                        returned even if its gap direction does not match.
          */
-        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir) {
+        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
+                boolean hasUnwantedGapAfter) {
             if (mFullSpanItems == null) {
                 return null;
             }
-            for (int i = 0; i < mFullSpanItems.size(); i++) {
+            final int limit = mFullSpanItems.size();
+            for (int i = 0; i < limit; i++) {
                 FullSpanItem fsi = mFullSpanItems.get(i);
                 if (fsi.mPosition >= maxPos) {
                     return null;
                 }
-                if (fsi.mPosition >= minPos && (gapDir == 0 || fsi.mGapDir == gapDir)) {
+                if (fsi.mPosition >= minPos
+                        && (gapDir == 0 || fsi.mGapDir == gapDir ||
+                        (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
                     return fsi;
                 }
             }
@@ -2436,10 +2526,15 @@
             int mPosition;
             int mGapDir;
             int[] mGapPerSpan;
+            // A full span may be laid out in primary direction but may have gaps due to
+            // invalidation of views after it. This is recorded during a reverse scroll and if
+            // view is still on the screen after scroll stops, we have to recalculate layout
+            boolean mHasUnwantedGapAfter;
 
             public FullSpanItem(Parcel in) {
                 mPosition = in.readInt();
                 mGapDir = in.readInt();
+                mHasUnwantedGapAfter = in.readInt() == 1;
                 int spanCount = in.readInt();
                 if (spanCount > 0) {
                     mGapPerSpan = new int[spanCount];
@@ -2467,6 +2562,7 @@
             public void writeToParcel(Parcel dest, int flags) {
                 dest.writeInt(mPosition);
                 dest.writeInt(mGapDir);
+                dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
                 if (mGapPerSpan != null && mGapPerSpan.length > 0) {
                     dest.writeInt(mGapPerSpan.length);
                     dest.writeIntArray(mGapPerSpan);
@@ -2480,6 +2576,7 @@
                 return "FullSpanItem{" +
                         "mPosition=" + mPosition +
                         ", mGapDir=" + mGapDir +
+                        ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter +
                         ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
                         '}';
             }
diff --git a/v7/recyclerview/src/android/support/v7/widget/util/SortedListAdapterCallback.java b/v7/recyclerview/src/android/support/v7/widget/util/SortedListAdapterCallback.java
new file mode 100644
index 0000000..4921541
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/util/SortedListAdapterCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget.util;
+
+import android.support.v7.util.SortedList;
+import android.support.v7.widget.RecyclerView;
+
+/**
+ * A {@link SortedList.Callback} implementation that can bind a {@link SortedList} to a
+ * {@link RecyclerView.Adapter}.
+ */
+public abstract class SortedListAdapterCallback<T2> extends SortedList.Callback<T2> {
+
+    final RecyclerView.Adapter mAdapter;
+
+    /**
+     * Creates a {@link SortedList.Callback} that will forward data change events to the provided
+     * Adapter.
+     *
+     * @param adapter The Adapter instance which should receive events from the SortedList.
+     */
+    public SortedListAdapterCallback(RecyclerView.Adapter adapter) {
+        mAdapter = adapter;
+    }
+
+    @Override
+    public void onInserted(int position, int count) {
+        mAdapter.notifyItemRangeInserted(position, count);
+    }
+
+    @Override
+    public void onRemoved(int position, int count) {
+        mAdapter.notifyItemRangeRemoved(position, count);
+    }
+
+    @Override
+    public void onMoved(int fromPosition, int toPosition) {
+        mAdapter.notifyItemMoved(fromPosition, toPosition);
+    }
+
+    @Override
+    public void onChanged(int position, int count) {
+        mAdapter.notifyItemRangeChanged(position, count);
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java b/v7/recyclerview/tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java
new file mode 100644
index 0000000..041526e
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.util;
+
+import junit.framework.TestCase;
+
+import static android.support.v7.util.SortedList.BatchedCallback.TYPE_NONE;
+import static android.support.v7.util.SortedList.BatchedCallback.TYPE_ADD;
+import static android.support.v7.util.SortedList.BatchedCallback.TYPE_REMOVE;
+import static android.support.v7.util.SortedList.BatchedCallback.TYPE_CHANGE;
+import static android.support.v7.util.SortedList.BatchedCallback.TYPE_MOVE;
+
+public class SortedListAdapterCallbackWrapperTest extends TestCase {
+
+    private int lastReceivedType = TYPE_NONE;
+    private int lastReceivedPosition = -1;
+    private int lastReceivedCount = -1;
+
+    private SortedList.Callback<Object> mCallback = new SortedList.Callback<Object>() {
+        @Override
+        public int compare(Object o1, Object o2) {
+            return 0;
+        }
+
+        @Override
+        public void onInserted(int position, int count) {
+            lastReceivedType = TYPE_ADD;
+            lastReceivedPosition = position;
+            lastReceivedCount = count;
+        }
+
+        @Override
+        public void onRemoved(int position, int count) {
+            lastReceivedType = TYPE_REMOVE;
+            lastReceivedPosition = position;
+            lastReceivedCount = count;
+        }
+
+        @Override
+        public void onMoved(int fromPosition, int toPosition) {
+            lastReceivedType = TYPE_MOVE;
+            lastReceivedPosition = fromPosition;
+            lastReceivedCount = toPosition;
+        }
+
+        @Override
+        public void onChanged(int position, int count) {
+            lastReceivedType = TYPE_CHANGE;
+            lastReceivedPosition = position;
+            lastReceivedCount = count;
+        }
+
+        @Override
+        public boolean areContentsTheSame(Object oldItem, Object newItem) {
+            return false;
+        }
+
+        @Override
+        public boolean areItemsTheSame(Object item1, Object item2) {
+            return false;
+        }
+    };
+
+    private SortedList.BatchedCallback<Object> mBatched =
+            new SortedList.BatchedCallback<Object>(mCallback);
+
+    public void testAdd() throws Throwable {
+        mBatched.onInserted(0, 3);
+        assertPending(TYPE_ADD, 0, 3);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 0, 3);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testRemove() throws Throwable {
+        mBatched.onRemoved(0, 3);
+        assertPending(TYPE_REMOVE, 0, 3);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_REMOVE, 0, 3);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testChange() throws Throwable {
+        mBatched.onChanged(0, 3);
+        assertPending(TYPE_CHANGE, 0, 3);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_CHANGE, 0, 3);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testMove() throws Throwable {
+        mBatched.onMoved(0, 3);
+        assertLast(TYPE_MOVE, 0, 3);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd1() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(3, 2);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 3, 7);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd2() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(1, 2);
+        assertLast(TYPE_ADD, 3, 5);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 1, 2);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd3() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(8, 2);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 3, 7);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd4() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(9, 2);
+        assertLast(TYPE_ADD, 3, 5);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 9, 2);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd5() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(4, 1);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 3, 6);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAdd6() throws Throwable {
+        mBatched.onInserted(3, 5);
+        mBatched.onInserted(4, 1);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.onInserted(4, 1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 3, 7);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchAddLoop() throws Throwable {
+        for (int i = 0; i < 10; i ++) {
+            mBatched.onInserted(4 + i, 1);
+            assertLast(TYPE_NONE, -1, -1);
+            assertPending(TYPE_ADD, 4, i + 1);
+        }
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 4, 10);
+    }
+
+    public void testBatchAddReverseLoop() throws Throwable {
+        for (int i = 10; i >= 0; i --) {
+            mBatched.onInserted(4, 1);
+            assertLast(TYPE_NONE, -1, -1);
+            assertPending(TYPE_ADD, 4, 10 - i + 1);
+        }
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 4, 11);
+    }
+
+    public void testBadBatchAddReverseLoop() throws Throwable {
+        for (int i = 10; i >= 0; i --) {
+            mBatched.onInserted(4 + i, 1);
+            if (i < 10) {
+                assertLast(TYPE_ADD, 4 + i + 1, 1);
+            }
+
+        }
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_ADD, 4, 1);
+    }
+
+    public void testBatchRemove1() throws Throwable {
+        mBatched.onRemoved(3, 5);
+        mBatched.onRemoved(3, 1);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_REMOVE, 3, 6);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchRemove2() throws Throwable {
+        mBatched.onRemoved(3, 5);
+        mBatched.onRemoved(4, 1);
+        assertLast(TYPE_REMOVE, 3, 5);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_REMOVE, 4, 1);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchRemove3() throws Throwable {
+        mBatched.onRemoved(3, 5);
+        mBatched.onRemoved(2, 3);
+        assertLast(TYPE_REMOVE, 3, 5);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_REMOVE, 2, 3);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchChange1() throws Throwable {
+        mBatched.onChanged(3, 5);
+        mBatched.onChanged(3, 1);
+        assertPending(TYPE_CHANGE, 3, 5);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_CHANGE, 3, 5);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchChange2() throws Throwable {
+        mBatched.onChanged(3, 5);
+        mBatched.onChanged(2, 7);
+        assertPending(TYPE_CHANGE, 2, 7);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.dispatchLastEvent();
+        assertLast(TYPE_CHANGE, 2, 7);
+        assertPending(TYPE_NONE, -1, -1);
+    }
+
+    public void testBatchChange3() throws Throwable {
+        mBatched.onChanged(3, 5);
+        mBatched.onChanged(2, 1);
+        assertLast(TYPE_NONE, -1, -1);
+        mBatched.onChanged(8, 2);
+        assertLast(TYPE_NONE, -1, -1);
+        assertPending(TYPE_CHANGE, 2, 8);
+    }
+
+    public void testBatchChange4() throws Throwable {
+        mBatched.onChanged(3, 5);
+        mBatched.onChanged(1, 1);
+        assertLast(TYPE_CHANGE, 3, 5);
+        assertPending(TYPE_CHANGE, 1, 1);
+    }
+
+    public void testBatchChange5() throws Throwable {
+        mBatched.onChanged(3, 5);
+        mBatched.onChanged(9, 1);
+        assertLast(TYPE_CHANGE, 3, 5);
+        assertPending(TYPE_CHANGE, 9, 1);
+    }
+
+    private void assertLast(int type, int position, int count) throws Throwable {
+        try {
+            assertEquals(lastReceivedType, type);
+            if (position >= 0) {
+                assertEquals(lastReceivedPosition, position);
+            }
+            if (count >= 0) {
+                assertEquals(lastReceivedCount, count);
+            }
+        } catch (Throwable t) {
+            throw new Throwable("last event: expected=" + log(type, position, count)
+                    + " found=" + log(lastReceivedType, lastReceivedPosition,
+                    lastReceivedCount), t);
+        }
+    }
+    private void assertPending(int type, int position, int count) throws Throwable {
+        try {
+            assertEquals(mBatched.mLastEventType, type);
+            if (position >= 0) {
+                assertEquals(mBatched.mLastEventPosition, position);
+            }
+            if (count >= 0) {
+                assertEquals(mBatched.mLastEventCount, count);
+            }
+        } catch (Throwable t) {
+            throw new Throwable("pending event: expected=" + log(type, position, count)
+                    + " found=" + log(mBatched.mLastEventType, mBatched.mLastEventPosition,
+                    mBatched.mLastEventCount), t);
+        }
+    }
+
+    private String log(int type, int position, int count) {
+        return TYPES_NAMES[type]
+                + ", p:" + position
+                + ", c:" + count;
+    }
+
+    private static final String[] TYPES_NAMES = new String[]{"none", "add", "remove", "change",
+            "move"};
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/SortedListTest.java b/v7/recyclerview/tests/src/android/support/v7/util/SortedListTest.java
new file mode 100644
index 0000000..d2da338
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/util/SortedListTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.util;
+
+import junit.framework.TestCase;
+
+import android.support.v7.util.SortedList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+
+public class SortedListTest extends TestCase {
+
+    SortedList<Item> mList;
+    List<Pair> mAdditions = new ArrayList<Pair>();
+    List<Pair> mRemovals = new ArrayList<Pair>();
+    List<Pair> mMoves = new ArrayList<Pair>();
+    List<Pair> mUpdates = new ArrayList<Pair>();
+    private SortedList.Callback<Item> mCallback;
+
+    private Comparator<? super Item> sItemComparator = new Comparator<Item>() {
+        @Override
+        public int compare(Item o1, Item o2) {
+            return mCallback.compare(o1, o2);
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mCallback = new SortedList.Callback<Item>() {
+            @Override
+            public int compare(Item o1, Item o2) {
+                return o1.cmpField < o2.cmpField ? -1 : (o1.cmpField == o2.cmpField ? 0 : 1);
+            }
+
+            @Override
+            public void onInserted(int position, int count) {
+                mAdditions.add(new Pair(position, count));
+            }
+
+            @Override
+            public void onRemoved(int position, int count) {
+                mRemovals.add(new Pair(position, count));
+            }
+
+            @Override
+            public void onMoved(int fromPosition, int toPosition) {
+                mMoves.add(new Pair(fromPosition, toPosition));
+            }
+
+            @Override
+            public void onChanged(int position, int count) {
+                mUpdates.add(new Pair(position, count));
+            }
+
+            @Override
+            public boolean areContentsTheSame(Item oldItem, Item newItem) {
+                return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
+            }
+
+            @Override
+            public boolean areItemsTheSame(Item item1, Item item2) {
+                return item1.id == item2.id;
+            }
+        };
+        mList = new SortedList<Item>(Item.class, mCallback);
+    }
+
+    public void testEmpty() {
+        assertEquals("empty", mList.size(), 0);
+    }
+
+    public void testAdd() {
+        Item item = new Item();
+        assertEquals(insert(item), 0);
+        assertEquals(size(), 1);
+        assertTrue(mAdditions.contains(new Pair(0, 1)));
+        Item item2 = new Item();
+        item2.cmpField = item.cmpField + 1;
+        assertEquals(insert(item2), 1);
+        assertEquals(size(), 2);
+        assertTrue(mAdditions.contains(new Pair(1, 1)));
+        Item item3 = new Item();
+        item3.cmpField = item.cmpField - 1;
+        mAdditions.clear();
+        assertEquals(insert(item3), 0);
+        assertEquals(size(), 3);
+        assertTrue(mAdditions.contains(new Pair(0, 1)));
+    }
+
+    public void testAddDuplicate() {
+        Item item = new Item();
+        Item item2 = new Item(item.id, item.cmpField);
+        item2.data = item.data;
+        insert(item);
+        assertEquals(0, insert(item2));
+        assertEquals(1, size());
+        assertEquals(1, mAdditions.size());
+        assertEquals(0, mUpdates.size());
+    }
+
+    public void testRemove() {
+        Item item = new Item();
+        assertFalse(remove(item));
+        assertEquals(0, mRemovals.size());
+        insert(item);
+        assertTrue(remove(item));
+        assertEquals(1, mRemovals.size());
+        assertTrue(mRemovals.contains(new Pair(0, 1)));
+        assertEquals(0, size());
+        assertFalse(remove(item));
+        assertEquals(1, mRemovals.size());
+    }
+
+    public void testRemove2() {
+        Item item = new Item();
+        Item item2 = new Item(item.cmpField);
+        insert(item);
+        assertFalse(remove(item2));
+        assertEquals(0, mRemovals.size());
+    }
+
+    public void testBatch() {
+        mList.beginBatchedUpdates();
+        for (int i = 0; i < 5; i ++) {
+            mList.add(new Item(i));
+        }
+        assertEquals(0, mAdditions.size());
+        mList.endBatchedUpdates();
+        assertTrue(mAdditions.contains(new Pair(0, 5)));
+    }
+
+    public void testRandom() throws Throwable {
+        Random random = new Random(System.nanoTime());
+        List<Item> copy = new ArrayList<Item>();
+        StringBuilder log = new StringBuilder();
+        try {
+            for (int i = 0; i < 10000; i++) {
+                switch (random.nextInt(3)) {
+                    case 0://ADD
+                        Item item = new Item();
+                        copy.add(item);
+                        insert(item);
+                        log.append("add " + item).append("\n");
+                        break;
+                    case 1://REMOVE
+                        if (copy.size() > 0) {
+                            int index = random.nextInt(mList.size());
+                            item = mList.get(index);
+                            log.append("remove " + item).append("\n");
+                            assertTrue(copy.remove(item));
+                            assertTrue(mList.remove(item));
+                        }
+                        break;
+                    case 2://UPDATE
+                        if (copy.size() > 0) {
+                            int index = random.nextInt(mList.size());
+                            item = mList.get(index);
+                            // TODO this cannot work
+                            Item newItem = new Item(item.id, item.cmpField);
+                            log.append("update " + item + " to " + newItem).append("\n");
+                            while (newItem.data == item.data) {
+                                newItem.data = random.nextInt(1000);
+                            }
+                            int itemIndex = mList.add(newItem);
+                            copy.remove(item);
+                            copy.add(newItem);
+                            assertSame(mList.get(itemIndex), newItem);
+                            assertNotSame(mList.get(index), item);
+                        }
+                        break;
+                    case 3:// UPDATE AT
+                        if (copy.size() > 0) {
+                            int index = random.nextInt(mList.size());
+                            item = mList.get(index);
+                            Item newItem = new Item(item.id, random.nextInt());
+                            mList.updateItemAt(index, newItem);
+                            copy.remove(item);
+                            copy.add(newItem);
+                        }
+                }
+                int lastCmp = Integer.MIN_VALUE;
+                for (int index = 0; index < copy.size(); index ++) {
+                    assertFalse(mList.indexOf(copy.get(index)) == SortedList.INVALID_POSITION);
+                    assertTrue(mList.get(index).cmpField >= lastCmp);
+                    lastCmp = mList.get(index).cmpField;
+                    assertTrue(copy.contains(mList.get(index)));
+                }
+
+                for (int index = 0; index < mList.size(); index ++) {
+                    assertNotNull(mList.mData[index]);
+                }
+                for (int index = mList.size(); index < mList.mData.length; index ++) {
+                    assertNull(mList.mData[index]);
+                }
+
+            }
+        } catch (Throwable t) {
+            Collections.sort(copy, sItemComparator);
+            log.append("Items:\n");
+            for (Item item : copy) {
+                log.append(item).append("\n");
+            }
+            log.append("SortedList:\n");
+            for (int i = 0; i < mList.size(); i ++) {
+                log.append(mList.get(i)).append("\n");
+            }
+
+            throw new Throwable(" \nlog:\n" + log.toString(), t);
+        }
+    }
+
+    private int size() {
+        return mList.size();
+    }
+
+    private int insert(Item item) {
+        return mList.add(item);
+    }
+
+    private boolean remove(Item item ) {
+        return mList.remove(item);
+    }
+
+    static class Item {
+        static int idCounter = 0;
+        final int id;
+
+        int cmpField;
+
+        int data = (int) (Math.random() * 1000);//used for comparison
+
+        public Item() {
+            id = idCounter ++;;
+            cmpField = (int) (Math.random() * 1000);
+        }
+
+        public Item(int cmpField) {
+            id = idCounter ++;;
+            this.cmpField = cmpField;
+        }
+
+        public Item(int id, int cmpField) {
+            this.id = id;
+            this.cmpField = cmpField;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Item item = (Item) o;
+
+            if (cmpField != item.cmpField) {
+                return false;
+            }
+            if (id != item.id) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = id;
+            result = 31 * result + cmpField;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Item{" +
+                    "id=" + id +
+                    ", cmpField=" + cmpField +
+                    ", data=" + data +
+                    '}';
+        }
+    }
+
+    private static final class Pair {
+        final int first, second;
+
+        public Pair(int first) {
+            this.first = first;
+            this.second = Integer.MIN_VALUE;
+        }
+
+        public Pair(int first, int second) {
+            this.first = first;
+            this.second = second;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Pair pair = (Pair) o;
+
+            if (first != pair.first) {
+                return false;
+            }
+            if (second != pair.second) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = first;
+            result = 31 * result + second;
+            return result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
index c7cb3ef..6ca7fd6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
@@ -19,6 +19,7 @@
 import junit.framework.AssertionFailedError;
 import junit.framework.TestResult;
 
+import android.os.Debug;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.widget.TextView;
@@ -40,7 +41,7 @@
 
     private static final String TAG = "AHT";
 
-    List<ViewHolder> mViewHolders;
+    List<RecyclerViewBasicTest.MockViewHolder> mViewHolders;
 
     AdapterHelper mAdapterHelper;
 
@@ -70,7 +71,7 @@
     private void cleanState() {
         mLog.setLength(0);
         mPreLayoutItems = new ArrayList<TestAdapter.Item>();
-        mViewHolders = new ArrayList<ViewHolder>();
+        mViewHolders = new ArrayList<RecyclerViewBasicTest.MockViewHolder>();
         mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
         mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
         mPreProcessClone = null;
@@ -129,7 +130,7 @@
                     for (int i = 0; i < updateOp.itemCount; i ++) {
                         // events are dispatched before view holders are updated for consistency
                         assertFalse("update op should not match any existing view holders",
-                                viewHolder.getPosition() == updateOp.positionStart + i);
+                                viewHolder.getLayoutPosition() == updateOp.positionStart + i);
                     }
                 }
 
@@ -198,10 +199,11 @@
         mPreProcessClone = mTestAdapter.createCopy();
     }
 
-    private void addViewHolder(int posiiton) {
-        ViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
+    private void addViewHolder(int position) {
+        RecyclerViewBasicTest.MockViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
                 new TextView(getContext()));
-        viewHolder.mPosition = posiiton;
+        viewHolder.mPosition = position;
+        viewHolder.mItem = mTestAdapter.mItems.get(position);
         mViewHolders.add(viewHolder);
     }
 
@@ -756,7 +758,7 @@
     public void testRandom() throws Throwable {
         mCollectLogs = true;
         Random random = new Random(System.nanoTime());
-        for (int i = 0; i < 250; i++) {
+        for (int i = 0; i < 100; i++) {
             try {
                 Log.d(TAG, "running random test " + i);
                 randomTest(random, Math.max(40, 10 + nextInt(random, i)));
@@ -835,6 +837,11 @@
     }
 
     void preProcess() {
+        for (RecyclerViewBasicTest.MockViewHolder vh : mViewHolders) {
+            final int ind = mTestAdapter.mItems.indexOf(vh.mItem);
+            assertEquals("actual adapter position should match", ind,
+                    mAdapterHelper.applyPendingUpdatesToPosition(vh.mPosition));
+        }
         mAdapterHelper.preProcess();
         for (int i = 0; i < mPreProcessClone.mItems.size(); i++) {
             TestAdapter.Item item = mPreProcessClone.mItems.get(i);
@@ -853,10 +860,10 @@
         }
         if (mViewHolders.size() > 0) {
             final String vhLog = vhLogBuilder.toString();
-            final int start = mViewHolders.get(0).getPosition();
+            final int start = mViewHolders.get(0).getLayoutPosition();
             for (int i = 1; i < mViewHolders.size(); i++) {
                 assertEquals("view holder positions should be continious in pre-layout" + vhLog,
-                        start + i, mViewHolders.get(i).getPosition());
+                        start + i, mViewHolders.get(i).getLayoutPosition());
             }
         }
         mAdapterHelper.consumePostponedUpdates();
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 204d965..f58bce2 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.os.Looper;
+import android.support.v4.view.ViewCompat;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.View;
@@ -26,7 +27,9 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -59,6 +62,19 @@
         }
     }
 
+    void setHasTransientState(final View view, final boolean value) {
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    ViewCompat.setHasTransientState(view, value);
+                }
+            });
+        } catch (Throwable throwable) {
+            Log.e(TAG, "", throwable);
+        }
+    }
+
     void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
         runTestOnUiThread(new Runnable() {
             @Override
@@ -73,18 +89,21 @@
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
+                try {
+                    mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
         });
+        checkForMainThreadException();
     }
 
     void postExceptionToInstrumentation(Throwable t) {
-        if (mDebug) {
-            Log.e(TAG, "captured exception on main thread", t);
-        }
         if (mainThreadException != null) {
             Log.e(TAG, "receiving another main thread exception. dropping.", t);
         } else {
+            Log.e(TAG, "captured exception on main thread", t);
             mainThreadException = t;
         }
 
@@ -108,14 +127,15 @@
             }
         }
         getInstrumentation().waitForIdleSync();
+        super.tearDown();
+
         try {
             checkForMainThreadException();
         } catch (Exception e) {
             throw e;
         } catch (Throwable throwable) {
-            throwable.printStackTrace();
+            throw new Exception(throwable);
         }
-        super.tearDown();
     }
 
     public Rect getDecoratedRecyclerViewBounds() {
@@ -131,13 +151,25 @@
         if (mRecyclerView == null) {
             return;
         }
-        mRecyclerView = null;
+        if (!isMainThread()) {
+            getInstrumentation().waitForIdleSync();
+        }
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
-                getActivity().mContainer.removeAllViews();
+                try {
+                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+                    if (adapter instanceof AttachDetachCountingAdapter) {
+                        ((AttachDetachCountingAdapter) adapter).getCounter()
+                                .validateRemaining(mRecyclerView);
+                    }
+                    getActivity().mContainer.removeAllViews();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
         });
+        mRecyclerView = null;
     }
 
     void waitForAnimations(int seconds) throws InterruptedException {
@@ -154,11 +186,27 @@
         }
     }
 
+    public boolean requestFocus(final View view) {
+        final boolean[] result = new boolean[1];
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = view.requestFocus();
+            }
+        });
+        return result[0];
+    }
+
     public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
         setRecyclerView(recyclerView, true);
     }
     public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
             throws Throwable {
+        setRecyclerView(recyclerView, true, true);
+    }
+    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool,
+            boolean addPositionCheckItemAnimator)
+            throws Throwable {
         mRecyclerView = recyclerView;
         if (assignDummyPool) {
             RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
@@ -177,11 +225,26 @@
 
                 @Override
                 public void putRecycledView(RecyclerView.ViewHolder scrap) {
+                    assertNull(scrap.mOwnerRecyclerView);
                     super.putRecycledView(scrap);
                 }
             };
             mRecyclerView.setRecycledViewPool(pool);
         }
+        if (addPositionCheckItemAnimator) {
+            mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+                @Override
+                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                        RecyclerView.State state) {
+                    RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
+                    if (!vh.isRemoved()) {
+                        assertNotSame("If getItemOffsets is called, child should have a valid"
+                                            + " adapter position unless it is removed : " + vh,
+                                    vh.getAdapterPosition(), RecyclerView.NO_POSITION);
+                    }
+                }
+            });
+        }
         mAdapterHelper = recyclerView.mAdapterHelper;
         runTestOnUiThread(new Runnable() {
             @Override
@@ -195,27 +258,35 @@
         return getActivity().mContainer;
     }
 
-    public void requestLayoutOnUIThread(final View view) throws Throwable {
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                view.requestLayout();
-            }
-        });
+    public void requestLayoutOnUIThread(final View view) {
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    view.requestLayout();
+                }
+            });
+        } catch (Throwable throwable) {
+            Log.e(TAG, "", throwable);
+        }
     }
 
-    public void scrollBy(final int dt) throws Throwable {
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
-                    mRecyclerView.scrollBy(dt, 0);
-                } else {
-                    mRecyclerView.scrollBy(0, dt);
-                }
+    public void scrollBy(final int dt) {
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
+                        mRecyclerView.scrollBy(dt, 0);
+                    } else {
+                        mRecyclerView.scrollBy(0, dt);
+                    }
 
-            }
-        });
+                }
+            });
+        } catch (Throwable throwable) {
+            Log.e(TAG, "", throwable);
+        }
     }
 
     void scrollToPosition(final int position) throws Throwable {
@@ -229,13 +300,17 @@
 
     void smoothScrollToPosition(final int position)
             throws Throwable {
-        Log.d(TAG, "SMOOTH scrolling to " + position);
+        if (mDebug) {
+            Log.d(TAG, "SMOOTH scrolling to " + position);
+        }
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
                 mRecyclerView.smoothScrollToPosition(position);
             }
         });
+        getInstrumentation().waitForIdleSync();
+        Thread.sleep(200); //give scroller some time so start
         while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
                 mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
             if (mDebug) {
@@ -243,13 +318,15 @@
             }
             Thread.sleep(200);
         }
-        Log.d(TAG, "SMOOTH scrolling done");
+        if (mDebug) {
+            Log.d(TAG, "SMOOTH scrolling done");
+        }
         getInstrumentation().waitForIdleSync();
     }
 
     class TestViewHolder extends RecyclerView.ViewHolder {
 
-        Item mBindedItem;
+        Item mBoundItem;
 
         public TestViewHolder(View itemView) {
             super(itemView);
@@ -258,7 +335,7 @@
 
         @Override
         public String toString() {
-            return super.toString() + " item:" + mBindedItem;
+            return super.toString() + " item:" + mBoundItem;
         }
     }
 
@@ -303,7 +380,7 @@
             while (i-- > 0) {
                 View view = getChildAt(i);
                 RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
-                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
+                Item item = ((TestViewHolder) lp.mViewHolder).mBoundItem;
                 if (mDebug) {
                     Log.d(TAG, "testing item " + i);
                 }
@@ -407,8 +484,10 @@
         }
     }
 
-    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+    class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
+            implements AttachDetachCountingAdapter {
 
+        ViewAttachDetachCounter mAttachmentCounter = new ViewAttachDetachCounter();
         List<Item> mItems;
 
         TestAdapter(int count) {
@@ -419,6 +498,30 @@
         }
 
         @Override
+        public void onViewAttachedToWindow(TestViewHolder holder) {
+            super.onViewAttachedToWindow(holder);
+            mAttachmentCounter.onViewAttached(holder);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(TestViewHolder holder) {
+            super.onViewDetachedFromWindow(holder);
+            mAttachmentCounter.onViewDetached(holder);
+        }
+
+        @Override
+        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+            super.onAttachedToRecyclerView(recyclerView);
+            mAttachmentCounter.onAttached(recyclerView);
+        }
+
+        @Override
+        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+            super.onDetachedFromRecyclerView(recyclerView);
+            mAttachmentCounter.onDetached(recyclerView);
+        }
+
+        @Override
         public TestViewHolder onCreateViewHolder(ViewGroup parent,
                 int viewType) {
             return new TestViewHolder(new TextView(parent.getContext()));
@@ -426,9 +529,25 @@
 
         @Override
         public void onBindViewHolder(TestViewHolder holder, int position) {
+            assertNotNull(holder.mOwnerRecyclerView);
+            assertEquals(position, holder.getAdapterPosition());
             final Item item = mItems.get(position);
             ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
-            holder.mBindedItem = item;
+            holder.mBoundItem = item;
+        }
+
+        @Override
+        public void onViewRecycled(TestViewHolder holder) {
+            super.onViewRecycled(holder);
+            final int adapterPosition = holder.getAdapterPosition();
+            final boolean shouldHavePosition = !holder.isRemoved() && holder.isBound() &&
+                    !holder.isAdapterPositionUnknown() && !holder.isInvalid();
+            String log = "Position check for " + holder.toString();
+            assertEquals(log, shouldHavePosition, adapterPosition != RecyclerView.NO_POSITION);
+            if (shouldHavePosition) {
+                assertTrue(log, mItems.size() > adapterPosition);
+                assertSame(log, holder.mBoundItem, mItems.get(adapterPosition));
+            }
         }
 
         public void deleteAndNotify(final int start, final int count) throws Throwable {
@@ -525,6 +644,9 @@
             return mItems.size();
         }
 
+        /**
+         * Uses notifyDataSetChanged
+         */
         public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
             for (int i = 0; i < fromToTuples.length; i += 1) {
                 int[] tuple = fromToTuples[i];
@@ -535,6 +657,9 @@
             }
         }
 
+        /**
+         * Uses notifyDataSetChanged
+         */
         public void moveItem(final int from, final int to, final boolean notifyChange)
                 throws Throwable {
             runTestOnUiThread(new Runnable() {
@@ -551,6 +676,29 @@
             });
         }
 
+        /**
+         * Uses notifyItemMoved
+         */
+        public void moveAndNotify(final int from, final int to) throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Item item = mItems.remove(from);
+                    mItems.add(to, item);
+                    offsetOriginalIndices(from, to - 1);
+                    item.mAdapterIndex = to;
+                    notifyItemMoved(from, to);
+                }
+            });
+        }
+
+
+
+        @Override
+        public ViewAttachDetachCounter getCounter() {
+            return mAttachmentCounter;
+        }
+
 
         private class AddRemoveRunnable implements Runnable {
             final int[][] mStartCountTuples;
@@ -598,6 +746,10 @@
         }
     }
 
+    public boolean isMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
     @Override
     public void runTestOnUiThread(Runnable r) throws Throwable {
         if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -626,4 +778,58 @@
                     '}';
         }
     }
+
+    public interface AttachDetachCountingAdapter {
+
+        ViewAttachDetachCounter getCounter();
+    }
+
+    public class ViewAttachDetachCounter {
+
+        Set<RecyclerView.ViewHolder> mAttachedSet = new HashSet<RecyclerView.ViewHolder>();
+
+        public void validateRemaining(RecyclerView recyclerView) {
+            final int childCount = recyclerView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View view = recyclerView.getChildAt(i);
+                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
+                assertTrue("remaining view should be in attached set " + vh,
+                        mAttachedSet.contains(vh));
+            }
+            assertEquals("there should not be any views left in attached set",
+                    childCount, mAttachedSet.size());
+        }
+
+        public void onViewDetached(RecyclerView.ViewHolder viewHolder) {
+            try {
+                assertTrue("view holder should be in attached set",
+                        mAttachedSet.remove(viewHolder));
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+        }
+
+        public void onViewAttached(RecyclerView.ViewHolder viewHolder) {
+            try {
+                assertTrue("view holder should not be in attached set",
+                        mAttachedSet.add(viewHolder));
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+        }
+
+        public void onAttached(RecyclerView recyclerView) {
+            // when a new RV is attached, clear the set and add all view holders
+            mAttachedSet.clear();
+            final int childCount = recyclerView.getChildCount();
+            for (int i = 0; i < childCount; i ++) {
+                View view = recyclerView.getChildAt(i);
+                mAttachedSet.add(recyclerView.getChildViewHolder(view));
+            }
+        }
+
+        public void onDetached(RecyclerView recyclerView) {
+            validateRemaining(recyclerView);
+        }
+    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
index a453e6e..46833ca 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -16,30 +16,41 @@
 
 package android.support.v7.widget;
 
+import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public class DefaultItemAnimatorTest extends ActivityInstrumentationTestCase2<TestActivity> {
 
+    private static final String TAG = "DefaultItemAnimatorTest";
+    Throwable mainThreadException;
+
     DefaultItemAnimator mAnimator;
     Adapter mAdapter;
     ViewGroup mDummyParent;
-    CountDownLatch mExpectedItems;
+    List<RecyclerView.ViewHolder> mExpectedItems = new ArrayList<RecyclerView.ViewHolder>();
 
     Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>();
     Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>();
     Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>();
     Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>();
 
+    Semaphore mExpectedItemCount = new Semaphore(0);
+
     public DefaultItemAnimatorTest() {
         super("android.support.v7.recyclerview", TestActivity.class);
     }
@@ -53,86 +64,254 @@
         mAnimator.setListener(new RecyclerView.ItemAnimator.ItemAnimatorListener() {
             @Override
             public void onRemoveFinished(RecyclerView.ViewHolder item) {
-                assertTrue(mRemoveFinished.add(item));
-                onFinished();
+                try {
+                    assertTrue(mRemoveFinished.add(item));
+                    onFinished(item);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
 
             @Override
             public void onAddFinished(RecyclerView.ViewHolder item) {
-                assertTrue(mAddFinished.add(item));
-                onFinished();
+                try {
+                    assertTrue(mAddFinished.add(item));
+                    onFinished(item);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
 
             @Override
             public void onMoveFinished(RecyclerView.ViewHolder item) {
-                assertTrue(mMoveFinished.add(item));
-                onFinished();
+                try {
+                    assertTrue(mMoveFinished.add(item));
+                    onFinished(item);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
 
             @Override
             public void onChangeFinished(RecyclerView.ViewHolder item) {
-                assertTrue(mChangeFinished.add(item));
-                onFinished();
+                try {
+                    assertTrue(mChangeFinished.add(item));
+                    onFinished(item);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
             }
 
-            private void onFinished() {
-                if (mExpectedItems != null) {
-                    mExpectedItems.countDown();
-                }
+            private void onFinished(RecyclerView.ViewHolder item) {
+                assertNotNull(mExpectedItems.remove(item));
+                mExpectedItemCount.release(1);
             }
         });
     }
 
-    void expectItems(int count) {
-        mExpectedItems = new CountDownLatch(count);
+    void checkForMainThreadException() throws Throwable {
+        if (mainThreadException != null) {
+            throw mainThreadException;
+        }
     }
 
-    void runAndWait(int seconds) throws Throwable {
+    @Override
+    protected void tearDown() throws Exception {
+        getInstrumentation().waitForIdleSync();
+        super.tearDown();
+        try {
+            checkForMainThreadException();
+        } catch (Exception e) {
+            throw e;
+        } catch (Throwable throwable) {
+            throw new Exception(throwable);
+        }
+    }
+
+    void expectItems(RecyclerView.ViewHolder... viewHolders) {
+        mExpectedItems.addAll(Arrays.asList(viewHolders));
+    }
+
+    void runAndWait(int itemCount, int seconds) throws Throwable {
+        runAndWait(itemCount, seconds, null);
+    }
+
+    void runAndWait(int itemCount, int seconds, final ThrowingRunnable postRun) throws Throwable {
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
                 mAnimator.runPendingAnimations();
+                if (postRun != null) {
+                    try {
+                        postRun.run();
+                    } catch (Throwable e) {
+                        throw new RuntimeException(e);
+                    }
+                }
             }
         });
-        waitForItems(seconds);
+        waitForItems(itemCount, seconds);
+        checkForMainThreadException();
     }
 
-    void waitForItems(int seconds) throws InterruptedException {
-        mExpectedItems.await(seconds, TimeUnit.SECONDS);
-        assertEquals("all expected finish events should happen", 0, mExpectedItems.getCount());
+    void waitForItems(int itemCount, int seconds) throws InterruptedException {
+        assertTrue("all vh animations should end",
+                mExpectedItemCount.tryAcquire(itemCount, seconds, TimeUnit.SECONDS));
+        assertEquals("all expected finish events should happen", 0, mExpectedItems.size());
+        // wait one more second for unwanted
+        assertFalse("should not receive any more permits",
+                mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS));
     }
 
     public void testAnimateAdd() throws Throwable {
         ViewHolder vh = createViewHolder(1);
-        expectItems(1);
+        expectItems(vh);
         assertTrue(animateAdd(vh));
         assertTrue(mAnimator.isRunning());
-        runAndWait(1);
+        runAndWait(1, 1);
     }
 
     public void testAnimateRemove() throws Throwable {
         ViewHolder vh = createViewHolder(1);
-        expectItems(1);
+        expectItems(vh);
         assertTrue(animateRemove(vh));
         assertTrue(mAnimator.isRunning());
-        runAndWait(1);
+        runAndWait(1, 1);
     }
 
     public void testAnimateMove() throws Throwable {
         ViewHolder vh = createViewHolder(1);
-        expectItems(1);
+        expectItems(vh);
         assertTrue(animateMove(vh, 0, 0, 100, 100));
         assertTrue(mAnimator.isRunning());
-        runAndWait(1);
+        runAndWait(1, 1);
     }
 
     public void testAnimateChange() throws Throwable {
         ViewHolder vh = createViewHolder(1);
         ViewHolder vh2 = createViewHolder(2);
-        expectItems(2);
+        expectItems(vh, vh2);
         assertTrue(animateChange(vh, vh2, 0, 0, 100, 100));
         assertTrue(mAnimator.isRunning());
-        runAndWait(1);
+        runAndWait(2, 1);
+    }
+
+    public void cancelBefore(int count, final RecyclerView.ViewHolder... toCancel)
+            throws Throwable {
+        cancelTest(true, count, toCancel);
+    }
+
+    public void cancelAfter(int count, final RecyclerView.ViewHolder... toCancel)
+            throws Throwable {
+        cancelTest(false, count, toCancel);
+    }
+
+    public void cancelTest(boolean before, int count, final RecyclerView.ViewHolder... toCancel) throws Throwable {
+        if (before) {
+            endAnimations(toCancel);
+            runAndWait(count, 1);
+        } else {
+            runAndWait(count, 1, new ThrowingRunnable() {
+                @Override
+                public void run() throws Throwable {
+                    endAnimations(toCancel);
+                }
+            });
+        }
+    }
+
+    public void testCancelAddBefore() throws Throwable {
+        final ViewHolder vh = createViewHolder(1);
+        expectItems(vh);
+        assertTrue(animateAdd(vh));
+        cancelBefore(1, vh);
+    }
+
+    public void testCancelAddAfter() throws Throwable {
+        final ViewHolder vh = createViewHolder(1);
+        expectItems(vh);
+        assertTrue(animateAdd(vh));
+        cancelAfter(1, vh);
+    }
+
+    public void testCancelMoveBefore() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(vh);
+        assertTrue(animateMove(vh, 10, 10, 100, 100));
+        cancelBefore(1, vh);
+    }
+
+    public void testCancelMoveAfter() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(vh);
+        assertTrue(animateMove(vh, 10, 10, 100, 100));
+        cancelAfter(1, vh);
+    }
+
+    public void testCancelRemove() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(vh);
+        assertTrue(animateRemove(vh));
+        endAnimations(vh);
+        runAndWait(1, 1);
+    }
+
+    public void testCancelChangeOldBefore() throws Throwable {
+        cancelChangeOldTest(true);
+    }
+    public void testCancelChangeOldAfter() throws Throwable {
+        cancelChangeOldTest(false);
+    }
+
+    public void cancelChangeOldTest(boolean before) throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        ViewHolder vh2 = createViewHolder(1);
+        expectItems(vh, vh2);
+        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+        cancelTest(before, 2, vh);
+    }
+
+    public void testCancelChangeNewBefore() throws Throwable {
+        cancelChangeNewTest(true);
+    }
+
+    public void testCancelChangeNewAfter() throws Throwable {
+        cancelChangeNewTest(false);
+    }
+
+    public void cancelChangeNewTest(boolean before) throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        ViewHolder vh2 = createViewHolder(1);
+        expectItems(vh, vh2);
+        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+        cancelTest(before, 2, vh2);
+    }
+
+    public void testCancelChangeBothBefore() throws Throwable {
+        cancelChangeBothTest(true);
+    }
+
+    public void testCancelChangeBothAfter() throws Throwable {
+        cancelChangeBothTest(false);
+    }
+
+    public void cancelChangeBothTest(boolean before) throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        ViewHolder vh2 = createViewHolder(1);
+        expectItems(vh, vh2);
+        assertTrue(animateChange(vh, vh2, 20, 20, 100, 100));
+        cancelTest(before, 2, vh, vh2);
+    }
+
+    void endAnimations(final RecyclerView.ViewHolder... vhs) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (RecyclerView.ViewHolder vh : vhs) {
+                    mAnimator.endAnimation(vh);
+                }
+            }
+        });
     }
 
     boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable {
@@ -195,6 +374,14 @@
         return vh;
     }
 
+    void postExceptionToInstrumentation(Throwable t) {
+        if (mainThreadException == null) {
+            mainThreadException = t;
+        } else {
+            Log.e(TAG, "skipping secondary main thread exception", t);
+        }
+    }
+
 
     private class Adapter extends RecyclerView.Adapter<ViewHolder> {
 
@@ -236,4 +423,17 @@
             ((TextView) itemView).setText(text);
         }
     }
+
+    private interface ThrowingRunnable {
+        public void run() throws Throwable;
+    }
+
+    @Override
+    public void runTestOnUiThread(Runnable r) throws Throwable {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            r.run();
+        } else {
+            super.runTestOnUiThread(r);
+        }
+    }
 }
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 a5c39d1..c02a0f6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -82,6 +82,51 @@
         mGlm.waitForLayout(2);
     }
 
+    public void testCustomWidthInHorizontal() throws Throwable {
+        customSizeInScrollDirectionTest(new Config(3, HORIZONTAL, false));
+    }
+
+    public void testCustomHeightInVertical() throws Throwable {
+        customSizeInScrollDirectionTest(new Config(3, VERTICAL, false));
+    }
+
+    public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
+        final int[] sizePerPosition = new int[]{3, 5, 9, 21, 3, 5, 9, 6, 9, 1};
+        final int[] expectedSizePerPosition = new int[]{9, 9, 9, 21, 3, 5, 9, 9, 9, 1};
+        final GridTestAdapter testAdapter = new GridTestAdapter(10) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
+                if (layoutParams == null) {
+                    layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT);
+                    holder.itemView.setLayoutParams(layoutParams);
+                }
+                final int size = sizePerPosition[position];
+                if (config.mOrientation == HORIZONTAL) {
+                    layoutParams.width = size;
+                } else {
+                    layoutParams.height = size;
+                }
+            }
+        };
+        testAdapter.setFullSpan(3, 5);
+        final RecyclerView rv = setupBasic(config, testAdapter);
+        waitForFirstLayout(rv);
+
+        assertTrue("[test sanity] some views should be laid out", mRecyclerView.getChildCount() > 0);
+        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+            View child = mRecyclerView.getChildAt(i);
+            final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
+                    : child.getHeight();
+            assertEquals("child " + i + " should have the size specified in its layout params",
+                    expectedSizePerPosition[i], size);
+        }
+        checkForMainThreadException();
+    }
+
     public void testLayoutParams() throws Throwable {
         layoutParamsTest(GridLayoutManager.HORIZONTAL);
         removeRecyclerView();
@@ -103,7 +148,7 @@
                 .getCompatAccessibilityDelegate().getItemDelegate();
         final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
         final View chosen = recyclerView.getChildAt(recyclerView.getChildCount() - 2);
-        final int position = recyclerView.getChildPosition(chosen);
+        final int position = recyclerView.getChildLayoutPosition(chosen);
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -178,17 +223,23 @@
         waitForFirstLayout(rv);
         final OrientationHelper helper = mGlm.mOrientationHelper;
         final int firstRowSize = Math.max(30, getSize(mGlm.findViewByPosition(2)));
-        assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(0)));
-        assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(1)));
-        assertEquals(firstRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(2)));
+        assertEquals(firstRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(0)));
+        assertEquals(firstRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(1)));
+        assertEquals(firstRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(2)));
         assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(0)));
         assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(1)));
         assertEquals(firstRowSize, getSize(mGlm.findViewByPosition(2)));
 
         final int secondRowSize = Math.max(200, getSize(mGlm.findViewByPosition(3)));
-        assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(3)));
-        assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(4)));
-        assertEquals(secondRowSize, helper.getDecoratedMeasurement(mGlm.findViewByPosition(5)));
+        assertEquals(secondRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(3)));
+        assertEquals(secondRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(4)));
+        assertEquals(secondRowSize,
+                helper.getDecoratedMeasurement(mGlm.findViewByPosition(5)));
         assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(3)));
         assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(4)));
         assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(5)));
@@ -456,7 +507,7 @@
                 while (visited < mAdapter.getItemCount()) {
                     for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
                         View child = mRecyclerView.getChildAt(i);
-                        final int pos = mRecyclerView.getChildPosition(child);
+                        final int pos = mRecyclerView.getChildLayoutPosition(child);
                         if (globalPositions[pos] != Integer.MIN_VALUE) {
                             continue;
                         }
@@ -503,7 +554,7 @@
                 while (!shouldTest.isEmpty() && scrollAmount != 0) {
                     for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
                         View child = mRecyclerView.getChildAt(i);
-                        int pos = mRecyclerView.getChildPosition(child);
+                        int pos = mRecyclerView.getChildLayoutPosition(child);
                         if (!shouldTest.get(pos)) {
                             continue;
                         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index 8adbedf..f304941 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -23,6 +23,7 @@
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -90,7 +91,8 @@
     }
 
     void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
-        mRecyclerView = new RecyclerView(getActivity());
+        mRecyclerView = new WrappedRecyclerView(getActivity());
+
         mRecyclerView.setHasFixedSize(true);
         mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
                 : config.mTestAdapter;
@@ -109,7 +111,7 @@
         setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
         int center = (mLayoutManager.findLastVisibleItemPosition()
                 - mLayoutManager.findFirstVisibleItemPosition()) / 2;
-        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(center);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
         final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
         runTestOnUiThread(new Runnable() {
             @Override
@@ -125,13 +127,142 @@
         center += childCountToAdd; // offset item
         mLayoutManager.waitForLayout(2);
         mLayoutManager.waitForAnimationsToEnd(20);
-        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForPosition(center);
+        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForLayoutPosition(center);
         assertNotNull("focused child should stay in layout", postVH);
         assertSame("same view holder should be kept for unchanged child", vh, postVH);
         assertEquals("focused child's screen position should stay unchanged", top,
                 mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
     }
 
+    public void testKeepFullFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true);
+    }
+
+    public void testKeepPartialFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false);
+    }
+
+    public void testKeepReverseFullFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true);
+    }
+
+    public void testKeepReversePartialFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false);
+    }
+
+    public void testKeepStackFromEndFullFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true);
+    }
+
+    public void testKeepStackFromEndPartialFocusOnResize() throws Throwable {
+        keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false);
+    }
+
+    public void keepFocusOnResizeTest(final Config config, boolean fullyVisible) throws Throwable {
+        setupByConfig(config, true);
+        final int targetPosition;
+        if (config.mStackFromEnd) {
+            targetPosition = mLayoutManager.findFirstVisibleItemPosition();
+        } else {
+            targetPosition = mLayoutManager.findLastVisibleItemPosition();
+        }
+        final OrientationHelper helper = mLayoutManager.mOrientationHelper;
+        final RecyclerView.ViewHolder vh = mRecyclerView
+                .findViewHolderForLayoutPosition(targetPosition);
+
+        // scroll enough to offset the child
+        int startMargin = helper.getDecoratedStart(vh.itemView) -
+                helper.getStartAfterPadding();
+        int endMargin = helper.getEndAfterPadding() -
+                helper.getDecoratedEnd(vh.itemView);
+        Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin);
+        requestFocus(vh.itemView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue("view should gain the focus", vh.itemView.hasFocus());
+            }
+        });
+        do {
+            Thread.sleep(100);
+        } while (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE);
+        // scroll enough to offset the child
+        startMargin = helper.getDecoratedStart(vh.itemView) -
+                helper.getStartAfterPadding();
+        endMargin = helper.getEndAfterPadding() -
+                helper.getDecoratedEnd(vh.itemView);
+
+        Log.d(TAG, "start margin " + startMargin + " , end margin:" + endMargin);
+        assertTrue("View should become fully visible", startMargin >= 0 && endMargin >= 0);
+
+        int expectedOffset = 0;
+        boolean offsetAtStart = false;
+        if (!fullyVisible) {
+            // move it a bit such that it is no more fully visible
+            final int childSize = helper
+                    .getDecoratedMeasurement(vh.itemView);
+            expectedOffset = childSize / 3;
+            if (startMargin < endMargin) {
+                scrollBy(expectedOffset);
+                offsetAtStart = true;
+            } else {
+                scrollBy(-expectedOffset);
+                offsetAtStart = false;
+            }
+            startMargin = helper.getDecoratedStart(vh.itemView) -
+                    helper.getStartAfterPadding();
+            endMargin = helper.getEndAfterPadding() -
+                    helper.getDecoratedEnd(vh.itemView);
+            assertTrue("test sanity, view should not be fully visible", startMargin < 0
+                    || endMargin < 0);
+        }
+
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final ViewGroup.LayoutParams layoutParams = mRecyclerView.getLayoutParams();
+                if (config.mOrientation == HORIZONTAL) {
+                    layoutParams.width = mRecyclerView.getWidth() / 2;
+                } else {
+                    layoutParams.height = mRecyclerView.getHeight() / 2;
+                }
+                mRecyclerView.setLayoutParams(layoutParams);
+            }
+        });
+        Thread.sleep(100);
+        // add a bunch of items right before that view, make sure it keeps its position
+        mLayoutManager.waitForLayout(2);
+        mLayoutManager.waitForAnimationsToEnd(20);
+        assertTrue("view should preserve the focus", vh.itemView.hasFocus());
+        final RecyclerView.ViewHolder postVH = mRecyclerView
+                .findViewHolderForLayoutPosition(targetPosition);
+        assertNotNull("focused child should stay in layout", postVH);
+        assertSame("same view holder should be kept for unchanged child", vh, postVH);
+        View focused = postVH.itemView;
+
+        startMargin = helper.getDecoratedStart(focused) - helper.getStartAfterPadding();
+        endMargin = helper.getEndAfterPadding() - helper.getDecoratedEnd(focused);
+
+        assertTrue("focused child should be somewhat visible",
+                helper.getDecoratedStart(focused) < helper.getEndAfterPadding()
+                        && helper.getDecoratedEnd(focused) > helper.getStartAfterPadding());
+        if (fullyVisible) {
+            assertTrue("focused child end should stay fully visible",
+                    endMargin >= 0);
+            assertTrue("focused child start should stay fully visible",
+                    startMargin >= 0);
+        } else {
+            if (offsetAtStart) {
+                assertTrue("start should preserve its offset", startMargin < 0);
+                assertTrue("end should be visible", endMargin >= 0);
+            } else {
+                assertTrue("end should preserve its offset", endMargin < 0);
+                assertTrue("start should be visible", startMargin >= 0);
+            }
+        }
+    }
+
     public void testResize() throws Throwable {
         for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
                 , Config.DEFAULT_ITEM_COUNT)) {
@@ -167,7 +298,7 @@
         while (testCount-- > 0) {
             // get middle child
             final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
-            final int position = mRecyclerView.getChildPosition(child);
+            final int position = mRecyclerView.getChildLayoutPosition(child);
             final int startOffset = config.mReverseLayout ?
                     orientationHelper.getEndAfterPadding() - orientationHelper
                             .getDecoratedEnd(child)
@@ -232,7 +363,7 @@
         int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
         for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
             View child = mLayoutManager.getChildAt(i);
-            int position = mRecyclerView.getChildPosition(child);
+            int position = mRecyclerView.getChildLayoutPosition(child);
             if (position < minPosition) {
                 minPosition = position;
             }
@@ -329,7 +460,7 @@
                     }
                 } else {
                     RecyclerView.ViewHolder vh =
-                            mRecyclerView.findViewHolderForPosition(scrollPosition);
+                            mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
                     assertNotNull("scroll to position should work", vh);
                     if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
                         assertEquals("scroll offset should be applied properly",
@@ -1059,7 +1190,7 @@
                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
                                 .getLayoutParams();
                         TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
-                        items.put(vh.mBindedItem, getViewBounds(child));
+                        items.put(vh.mBoundItem, getViewBounds(child));
                     }
                 }
             });
@@ -1163,4 +1294,26 @@
                     '}';
         }
     }
+
+    private static class WrappedRecyclerView extends RecyclerView {
+
+        public WrappedRecyclerView(Context context) {
+            super(context);
+            init(context);
+        }
+
+        public WrappedRecyclerView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            init(context);
+        }
+
+        public WrappedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+            init(context);
+        }
+
+        private void init(Context context) {
+            initializeScrollbars(null);
+        }
+    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
index 5051f57..ca57ce1 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/OpReorderTest.java
@@ -154,7 +154,7 @@
     }
 
     public void testRandom() throws Exception {
-        for (int i = 0; i < 250; i++) {
+        for (int i = 0; i < 150; i++) {
             try {
                 cleanState();
                 setup(50);
@@ -170,12 +170,11 @@
     }
 
     public void testRandomMoveRemove() throws Exception {
-        for (int i = 0; i < 10000; i++) {
+        for (int i = 0; i < 1000; i++) {
             try {
                 cleanState();
                 setup(5);
                 orderedRandom(MOVE, REMOVE);
-                Log.d(TAG, "running random move remove test " + i);
                 process();
             } catch (Throwable t) {
                 throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
@@ -184,12 +183,11 @@
     }
 
     public void testRandomMoveAdd() throws Exception {
-        for (int i = 0; i < 10000; i++) {
+        for (int i = 0; i < 1000; i++) {
             try {
                 cleanState();
                 setup(5);
                 orderedRandom(MOVE, ADD);
-                Log.d(TAG, "running random move add test " + i);
                 process();
             } catch (Throwable t) {
                 throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
@@ -198,12 +196,11 @@
     }
 
     public void testRandomMoveUpdate() throws Exception {
-        for (int i = 0; i < 10000; i++) {
+        for (int i = 0; i < 1000; i++) {
             try {
                 cleanState();
                 setup(5);
                 orderedRandom(MOVE, UPDATE);
-                Log.d(TAG, "running random move update test " + i);
                 process();
             } catch (Throwable t) {
                 throw new Exception(t.getMessage() + "\n" + opsToString(mUpdateOps));
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 0c08775..e09dafb 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -31,7 +31,6 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class RecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
@@ -278,13 +277,14 @@
         mRecyclerView.getItemAnimator().setSupportsChangeAnimations(supportsChangeAnim);
 
         final RecyclerView.ViewHolder toBeChangedVH =
-                mRecyclerView.findViewHolderForPosition(changedIndex);
+                mRecyclerView.findViewHolderForLayoutPosition(changedIndex);
         mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
             @Override
             void afterPreLayout(RecyclerView.Recycler recycler,
                     AnimationLayoutManager layoutManager,
                     RecyclerView.State state) {
-                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
+                        changedIndex);
                 if (supportsChangeAnim) {
                     assertTrue(logPrefix + " changed view holder should have correct flag"
                             , vh.isChanged());
@@ -297,7 +297,8 @@
             @Override
             void afterPostLayout(RecyclerView.Recycler recycler,
                     AnimationLayoutManager layoutManager, RecyclerView.State state) {
-                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(
+                        changedIndex);
                 assertFalse(logPrefix + "VH should not be marked as changed", vh.isChanged());
                 if (supportsChangeAnim) {
                     assertNotSame(logPrefix + "a new VH should be given if change is supported",
@@ -500,6 +501,7 @@
         int targetItemCount = mTestAdapter.getItemCount();
         for (int i = 0; i < 100; i++) {
             mTestAdapter.deleteAndNotify(new int[]{0, 1}, new int[]{7, 1});
+            checkForMainThreadException();
             targetItemCount -= 2;
         }
         // wait until main thread runnables are consumed
@@ -593,6 +595,40 @@
         });
     }
 
+    public void testNotifyDataSetChangedDuringScroll() throws Throwable {
+        setupBasic(10);
+        final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
+        final AtomicInteger onScrollItemCount = new AtomicInteger(0);
+
+        mLayoutManager.setOnLayoutCallbacks(new OnLayoutCallbacks() {
+            @Override
+            void onLayoutChildren(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager lm, RecyclerView.State state) {
+                onLayoutItemCount.set(state.getItemCount());
+                super.onLayoutChildren(recycler, lm, state);
+            }
+
+            @Override
+            public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+                onScrollItemCount.set(state.getItemCount());
+                super.onScroll(dx, recycler, state);
+            }
+        });
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.mItems.remove(5);
+                mTestAdapter.notifyDataSetChanged();
+                mRecyclerView.scrollBy(0, 100);
+                assertTrue("scrolling while there are pending adapter updates should "
+                        + "trigger a layout", mLayoutManager.mOnLayoutCallbacks.mLayoutCount > 0);
+                assertEquals("scroll by should be called w/ updated adapter count",
+                        mTestAdapter.mItems.size(), onScrollItemCount.get());
+
+            }
+        });
+    }
+
     public void testAddInvisibleAndVisible() throws Throwable {
         setupBasic(10, 1, 7);
         mLayoutManager.expectLayouts(2);
@@ -648,6 +684,17 @@
 
     public void testFindPositionOffset() throws Throwable {
         setupBasic(10);
+        mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+            @Override
+            void beforePreLayout(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager lm, RecyclerView.State state) {
+                super.beforePreLayout(recycler, lm, state);
+                // [0,2,4]
+                assertEquals("offset check", 0, mAdapterHelper.findPositionOffset(0));
+                assertEquals("offset check", 1, mAdapterHelper.findPositionOffset(2));
+                assertEquals("offset check", 2, mAdapterHelper.findPositionOffset(4));
+            }
+        };
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -656,14 +703,9 @@
                 mTestAdapter.notifyItemRangeRemoved(1, 1);
                 // delete 3
                 mTestAdapter.notifyItemRangeRemoved(2, 1);
-                mAdapterHelper.preProcess();
-                // [0,2,4]
-                assertEquals("offset check", 0, mAdapterHelper.findPositionOffset(0));
-                assertEquals("offset check", 1, mAdapterHelper.findPositionOffset(2));
-                assertEquals("offset check", 2, mAdapterHelper.findPositionOffset(4));
-
             }
         });
+        mLayoutManager.waitForLayout(2);
     }
 
     private void setLayoutRange(int start, int count) {
@@ -1136,7 +1178,7 @@
             for (int i = 0; i < childCount; i++) {
                 ViewHolder vh = getChildViewHolderInt(getChildAt(i));
                 TestViewHolder tvh = (TestViewHolder) vh;
-                log.append(tvh.mBindedItem).append(vh)
+                log.append(tvh.mBoundItem).append(vh)
                         .append(" hidden:")
                         .append(mChildHelper.mHiddenViews.contains(vh.itemView))
                         .append("\n");
@@ -1146,14 +1188,14 @@
                 if (vh.isInvalid()) {
                     continue;
                 }
-                if (vh.getPosition() < 0) {
+                if (vh.getLayoutPosition() < 0) {
                     LayoutManager lm = getLayoutManager();
                     for (int j = 0; j < lm.getChildCount(); j ++) {
                         assertNotSame("removed view holder should not be in LM's child list",
                                 vh.itemView, lm.getChildAt(j));
                     }
                 } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
-                    if (!existingOffsets.add(vh.getPosition())) {
+                    if (!existingOffsets.add(vh.getLayoutPosition())) {
                         throw new IllegalStateException("view holder position conflict for "
                                 + "existing views " + vh + "\n" + log);
                     }
@@ -1344,7 +1386,7 @@
                         viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
                         viewHolder.mPreLayoutPosition);
                 assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
-                        viewHolder.getPosition());
+                        viewHolder.getLayoutPosition());
                 if (mType == Type.scrap) {
                     assertEquals(this + ": old position should match\n" + log, mOldPos,
                             result.scrapResult.getOldPosition());
@@ -1352,7 +1394,7 @@
             } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
                     .isRemoved()) {
                 assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
-                        + viewHolder, mPostLayoutPos, viewHolder.getPosition());
+                        + viewHolder, mPostLayoutPos, viewHolder.getLayoutPosition());
             }
         }
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index 86e6dda..750d50f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.test.AndroidTestCase;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -36,14 +37,7 @@
     }
 
     public void testMeasureWithoutLayoutManager() {
-        Throwable measureThrowable = null;
-        try {
-            measure();
-        } catch (Throwable throwable) {
-            measureThrowable = throwable;
-        }
-        assertTrue("Calling measure without a layout manager should throw exception"
-                , measureThrowable instanceof NullPointerException);
+        measure();
     }
 
     private void measure() {
@@ -54,20 +48,57 @@
         mRecyclerView.layout(0, 0, 320, 320);
     }
 
-    private void safeLayout() {
-        try {
-            layout();
-        } catch (Throwable t) {
+    private void focusSearch() {
+        mRecyclerView.focusSearch(1);
+    }
 
-        }
+    public void testLayoutWithoutAdapter() throws InterruptedException {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        layout();
+        assertEquals("layout manager should not be called if there is no adapter attached",
+                0, layoutManager.mLayoutCount);
     }
 
     public void testLayoutWithoutLayoutManager() throws InterruptedException {
-        MockLayoutManager layoutManager = new MockLayoutManager();
-        mRecyclerView.setLayoutManager(layoutManager);
-        safeLayout();
-        assertEquals("layout manager should not be called if there is no adapter attached",
-                0, layoutManager.mLayoutCount);
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+    }
+
+    public void testFocusWithoutLayoutManager() throws InterruptedException {
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+        focusSearch();
+    }
+
+    public void testScrollWithoutLayoutManager() throws InterruptedException {
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+        mRecyclerView.scrollBy(10, 10);
+    }
+
+    public void testSmoothScrollWithoutLayoutManager() throws InterruptedException {
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+        mRecyclerView.smoothScrollBy(10, 10);
+    }
+
+    public void testScrollToPositionWithoutLayoutManager() throws InterruptedException {
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+        mRecyclerView.scrollToPosition(5);
+    }
+
+    public void testSmoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
+        mRecyclerView.setAdapter(new MockAdapter(20));
+        measure();
+        layout();
+        mRecyclerView.smoothScrollToPosition(5);
     }
 
     public void testLayout() throws InterruptedException {
@@ -329,7 +360,7 @@
     }
 
     static class MockViewHolder extends RecyclerView.ViewHolder {
-
+        public Object mItem;
         public MockViewHolder(View itemView) {
             super(itemView);
         }
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 62ede3a..178225d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -18,6 +18,7 @@
 package android.support.v7.widget;
 
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Debug;
 import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
@@ -28,6 +29,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -38,13 +40,14 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
 
 public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
 
     private static final String TAG = "RecyclerViewLayoutTest";
 
@@ -52,6 +55,562 @@
         super(DEBUG);
     }
 
+    public void testScrollToPositionCallback() throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        TestLayoutManager tlm = new TestLayoutManager() {
+            int scrollPos = RecyclerView.NO_POSITION;
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutLatch.countDown();
+                if (scrollPos == RecyclerView.NO_POSITION) {
+                    layoutRange(recycler, 0, 10);
+                } else {
+                    layoutRange(recycler, scrollPos, scrollPos + 10);
+                }
+            }
+
+            @Override
+            public void scrollToPosition(int position) {
+                scrollPos = position;
+                requestLayout();
+            }
+        };
+        recyclerView.setLayoutManager(tlm);
+        TestAdapter adapter = new TestAdapter(100);
+        recyclerView.setAdapter(adapter);
+        final AtomicInteger rvCounter = new AtomicInteger(0);
+        final AtomicInteger viewGroupCounter = new AtomicInteger(0);
+        recyclerView.getViewTreeObserver().addOnScrollChangedListener(
+                new ViewTreeObserver.OnScrollChangedListener() {
+                    @Override
+                    public void onScrollChanged() {
+                        viewGroupCounter.incrementAndGet();
+                    }
+                });
+        recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                rvCounter.incrementAndGet();
+                super.onScrolled(recyclerView, dx, dy);
+            }
+        });
+        tlm.expectLayouts(1);
+
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(2);
+        // wait for draw :/
+        Thread.sleep(1000);
+
+        assertEquals("RV on scroll should be called for initialization", 1, rvCounter.get());
+        assertEquals("VTO on scroll should be called for initialization", 1,
+                viewGroupCounter.get());
+        tlm.expectLayouts(1);
+        scrollToPosition(3);
+        tlm.waitForLayout(2);
+        assertEquals("RV on scroll should be called", 2, rvCounter.get());
+        assertEquals("VTO on scroll should be called", 2, viewGroupCounter.get());
+        tlm.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        tlm.waitForLayout(2);
+        // wait for draw :/
+        Thread.sleep(1000);
+        assertEquals("on scroll should NOT be called", 2, rvCounter.get());
+        assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
+
+    }
+
+    public void testScrollInBothDirectionEqual() throws Throwable {
+        scrollInBothDirection(3, 3, 1000, 1000);
+    }
+
+    public void testScrollInBothDirectionMoreVertical() throws Throwable {
+        scrollInBothDirection(2, 3, 1000, 1000);
+    }
+
+    public void testScrollInBothDirectionMoreHorizontal() throws Throwable {
+        scrollInBothDirection(3, 2, 1000, 1000);
+    }
+
+    public void testScrollHorizontalOnly() throws Throwable {
+        scrollInBothDirection(3, 0, 1000, 0);
+    }
+
+    public void testScrollVerticalOnly() throws Throwable {
+        scrollInBothDirection(0, 3, 0, 1000);
+    }
+
+    public void testScrollInBothDirectionEqualReverse() throws Throwable {
+        scrollInBothDirection(3, 3, -1000, -1000);
+    }
+
+    public void testScrollInBothDirectionMoreVerticalReverse() throws Throwable {
+        scrollInBothDirection(2, 3, -1000, -1000);
+    }
+
+    public void testScrollInBothDirectionMoreHorizontalReverse() throws Throwable {
+        scrollInBothDirection(3, 2, -1000, -1000);
+    }
+
+    public void testScrollHorizontalOnlyReverse() throws Throwable {
+        scrollInBothDirection(3, 0, -1000, 0);
+    }
+
+    public void testScrollVerticalOnlyReverse() throws Throwable {
+        scrollInBothDirection(0, 3, 0, -1000);
+    }
+
+    public void scrollInBothDirection(int horizontalScrollCount, int verticalScrollCount,
+            int horizontalVelocity, int verticalVelocity)
+            throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        final AtomicInteger horizontalCounter = new AtomicInteger(horizontalScrollCount);
+        final AtomicInteger verticalCounter = new AtomicInteger(verticalScrollCount);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public boolean canScrollHorizontally() {
+                return true;
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, 10);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                if (verticalCounter.get() > 0) {
+                    verticalCounter.decrementAndGet();
+                    return dy;
+                }
+                return 0;
+            }
+
+            @Override
+            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                if (horizontalCounter.get() > 0) {
+                    horizontalCounter.decrementAndGet();
+                    return dx;
+                }
+                return 0;
+            }
+        };
+        TestAdapter adapter = new TestAdapter(100);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(2);
+        assertTrue("test sanity, fling must run", fling(horizontalVelocity, verticalVelocity));
+        assertEquals("rv's horizontal scroll cb must run " + horizontalScrollCount + " times'", 0,
+                horizontalCounter.get());
+        assertEquals("rv's vertical scroll cb must run " + verticalScrollCount + " times'", 0,
+                verticalCounter.get());
+    }
+
+    public void testDraglHorizontal() throws Throwable {
+        scrollInOtherOrientationTest(true, true);
+    }
+
+    public void testDragVertical() throws Throwable {
+        scrollInOtherOrientationTest(false, true);
+    }
+
+    public void testFlingHorizontal() throws Throwable {
+        scrollInOtherOrientationTest(true, false);
+    }
+
+    public void testFlingVertical() throws Throwable {
+        scrollInOtherOrientationTest(false, false);
+    }
+
+
+    public void scrollInOtherOrientationTest(final boolean horizontal, final boolean drag)
+            throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        final AtomicBoolean scrolledHorizontal = new AtomicBoolean(false);
+        final AtomicBoolean scrolledVertical = new AtomicBoolean(false);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public boolean canScrollHorizontally() {
+                return horizontal;
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return !horizontal;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, 10);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                scrolledVertical.set(true);
+                return dy;
+            }
+
+            @Override
+            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                scrolledHorizontal.set(true);
+                return dx;
+            }
+        };
+        TestAdapter adapter = new TestAdapter(100);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(2);
+        if (drag) {
+            TouchUtils.dragViewTo(this, mRecyclerView, Gravity.LEFT | Gravity.TOP, 200, 200);
+        } else {// fling
+            assertTrue("test sanity, fling must run", fling(600, 600));
+        }
+        assertEquals("horizontal scroll", horizontal, scrolledHorizontal.get());
+        assertEquals("vertical scroll", !horizontal, scrolledVertical.get());
+    }
+
+    private boolean fling(final int velocityX, final int velocityY) throws Throwable {
+        final AtomicBoolean didStart = new AtomicBoolean(false);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                boolean result = mRecyclerView.fling(velocityX, velocityY);
+                didStart.set(result);
+            }
+        });
+        if (!didStart.get()) {
+            return false;
+        }
+        // cannot set scroll listener in case it is subject to some test so instead doing a busy
+        // loop until state goes idle
+        while (mRecyclerView.getScrollState() != SCROLL_STATE_IDLE) {
+            getInstrumentation().waitForIdleSync();
+        }
+        return true;
+    }
+
+    public void testTransientStateRecycleViaAdapter() throws Throwable {
+        transientStateRecycleTest(true, false);
+    }
+
+    public void testTransientStateRecycleViaTransientStateCleanup() throws Throwable {
+        transientStateRecycleTest(false, true);
+    }
+
+    public void testTransientStateDontRecycle() throws Throwable {
+        transientStateRecycleTest(false, false);
+    }
+
+    public void transientStateRecycleTest(final boolean succeed, final boolean unsetTransientState)
+            throws Throwable {
+        final List<View> failedToRecycle = new ArrayList<View>();
+        final List<View> recycled = new ArrayList<View>();
+        TestAdapter testAdapter = new TestAdapter(10) {
+            @Override
+            public boolean onFailedToRecycleView(
+                    TestViewHolder holder) {
+                failedToRecycle.add(holder.itemView);
+                if (unsetTransientState) {
+                    setHasTransientState(holder.itemView, false);
+                }
+                return succeed;
+            }
+
+            @Override
+            public void onViewRecycled(TestViewHolder holder) {
+                recycled.add(holder.itemView);
+                super.onViewRecycled(holder);
+            }
+        };
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                if (getChildCount() == 0) {
+                    detachAndScrapAttachedViews(recycler);
+                    layoutRange(recycler, 0, 5);
+                } else {
+                    removeAndRecycleAllViews(recycler);
+                }
+                if (layoutLatch != null) {
+                    layoutLatch.countDown();
+                }
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(tlm);
+        recyclerView.setItemAnimator(null);
+        setRecyclerView(recyclerView);
+        getInstrumentation().waitForIdleSync();
+        // make sure we have enough views after this position so that we'll receive the on recycled
+        // callback
+        View view = recyclerView.getChildAt(3);//this has to be greater than def cache size.
+        setHasTransientState(view, true);
+        tlm.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        tlm.waitForLayout(2);
+
+        assertTrue(failedToRecycle.contains(view));
+        assertEquals(succeed || unsetTransientState, recycled.contains(view));
+    }
+
+    public void testAdapterPositionInvalidation() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter adapter = new TestAdapter(10);
+        final TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, state.getItemCount());
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < tlm.getChildCount(); i++) {
+                    assertNotSame("adapter positions should not be undefined",
+                            recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
+                            RecyclerView.NO_POSITION);
+                }
+                adapter.notifyDataSetChanged();
+                for (int i = 0; i < tlm.getChildCount(); i++) {
+                    assertSame("adapter positions should be undefined",
+                            recyclerView.getChildAdapterPosition(tlm.getChildAt(i)),
+                            RecyclerView.NO_POSITION);
+                }
+            }
+        });
+    }
+
+    public void testAdapterPositionsBasic() throws Throwable {
+        adapterPositionsTest(null);
+    }
+
+    public void testAdapterPositionsRemoveItems() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.deleteAndNotify(3, 4);
+            }
+        });
+    }
+
+    public void testAdapterPositionsRemoveItemsBefore() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.deleteAndNotify(0, 1);
+            }
+        });
+    }
+
+    public void testAdapterPositionsAddItemsBefore() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.addAndNotify(0, 5);
+            }
+        });
+    }
+
+    public void testAdapterPositionsAddItemsInside() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.addAndNotify(3, 2);
+            }
+        });
+    }
+
+    public void testAdapterPositionsMoveItems() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.moveAndNotify(3, 5);
+            }
+        });
+    }
+
+    public void testAdapterPositionsNotifyDataSetChanged() throws Throwable {
+        adapterPositionsTest(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.mItems.clear();
+                for (int i = 0; i < 20; i++) {
+                    adapter.mItems.add(new Item(i, "added item"));
+                }
+                adapter.notifyDataSetChanged();
+            }
+        });
+    }
+
+    public void testAvoidLeakingRecyclerViewIfViewIsNotRecycled() throws Throwable {
+        final AtomicBoolean failedToRecycle = new AtomicBoolean(false);
+        RecyclerView rv = new RecyclerView(getActivity());
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, state.getItemCount());
+                layoutLatch.countDown();
+            }
+        };
+        TestAdapter adapter = new TestAdapter(10) {
+            @Override
+            public boolean onFailedToRecycleView(
+                    TestViewHolder holder) {
+                failedToRecycle.set(true);
+                return false;
+            }
+        };
+        rv.setAdapter(adapter);
+        rv.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(1);
+        final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewCompat.setHasTransientState(vh.itemView, true);
+            }
+        });
+        tlm.expectLayouts(1);
+        adapter.deleteAndNotify(0, 10);
+        tlm.waitForLayout(2);
+        final CountDownLatch animationsLatch = new CountDownLatch(1);
+        rv.getItemAnimator().isRunning(
+                new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
+                    @Override
+                    public void onAnimationsFinished() {
+                        animationsLatch.countDown();
+                    }
+                });
+        assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
+        assertTrue(failedToRecycle.get());
+        assertNull(vh.mOwnerRecyclerView);
+        checkForMainThreadException();
+    }
+
+    public void testAvoidLeakingRecyclerViewViaViewHolder() throws Throwable {
+        RecyclerView rv = new RecyclerView(getActivity());
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, state.getItemCount());
+                layoutLatch.countDown();
+            }
+        };
+        TestAdapter adapter = new TestAdapter(10);
+        rv.setAdapter(adapter);
+        rv.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(1);
+        final RecyclerView.ViewHolder vh = rv.getChildViewHolder(rv.getChildAt(0));
+        tlm.expectLayouts(1);
+        adapter.deleteAndNotify(0, 10);
+        tlm.waitForLayout(2);
+        final CountDownLatch animationsLatch = new CountDownLatch(1);
+        rv.getItemAnimator().isRunning(
+                new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
+                    @Override
+                    public void onAnimationsFinished() {
+                        animationsLatch.countDown();
+                    }
+                });
+        assertTrue(animationsLatch.await(2, TimeUnit.SECONDS));
+        assertNull(vh.mOwnerRecyclerView);
+        checkForMainThreadException();
+    }
+
+    public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
+        final TestAdapter testAdapter = new TestAdapter(10);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    layoutRange(recycler, Math.min(state.getItemCount(), 2)
+                            , Math.min(state.getItemCount(), 7));
+                    layoutLatch.countDown();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(tlm);
+        recyclerView.setAdapter(testAdapter);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    final int count = recyclerView.getChildCount();
+                    Map<View, Integer> layoutPositions = new HashMap<View, Integer>();
+                    assertTrue("test sanity", count > 0);
+                    for (int i = 0; i < count; i++) {
+                        View view = recyclerView.getChildAt(i);
+                        TestViewHolder vh = (TestViewHolder) recyclerView.getChildViewHolder(view);
+                        int index = testAdapter.mItems.indexOf(vh.mBoundItem);
+                        assertEquals("should be able to find VH with adapter position " + index, vh,
+                                recyclerView.findViewHolderForAdapterPosition(index));
+                        assertEquals("get adapter position should return correct index", index,
+                                vh.getAdapterPosition());
+                        layoutPositions.put(view, vh.mPosition);
+                    }
+                    if (adapterChanges != null) {
+                        adapterChanges.run(testAdapter);
+                        for (int i = 0; i < count; i++) {
+                            View view = recyclerView.getChildAt(i);
+                            TestViewHolder vh = (TestViewHolder) recyclerView
+                                    .getChildViewHolder(view);
+                            int index = testAdapter.mItems.indexOf(vh.mBoundItem);
+                            if (index >= 0) {
+                                assertEquals("should be able to find VH with adapter position "
+                                                + index, vh,
+                                        recyclerView.findViewHolderForAdapterPosition(index));
+                            }
+                            assertSame("get adapter position should return correct index", index,
+                                    vh.getAdapterPosition());
+                            assertSame("should be able to find view with layout position",
+                                    vh, mRecyclerView.findViewHolderForLayoutPosition(
+                                            layoutPositions.get(view)));
+                        }
+
+                    }
+
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+            }
+        });
+        checkForMainThreadException();
+    }
+
     public void testScrollStateForSmoothScroll() throws Throwable {
         TestAdapter testAdapter = new TestAdapter(10);
         TestLayoutManager tlm = new TestLayoutManager();
@@ -415,6 +974,7 @@
         if (removeItem) {
             final int newTarget = targetPosition - 10;
             testAdapter.deleteAndNotify(newTarget + 1, testAdapter.getItemCount() - newTarget - 1);
+            final CountDownLatch targetCheck = new CountDownLatch(1);
             runTestOnUiThread(new Runnable() {
                 @Override
                 public void run() {
@@ -427,15 +987,18 @@
                             } catch (Throwable t) {
                                 postExceptionToInstrumentation(t);
                             }
+                            targetCheck.countDown();
                         }
-                    }, 200);
+                    }, 50);
                 }
             });
+            assertTrue("target position should be checked on time ",
+                    targetCheck.await(10, TimeUnit.SECONDS));
             checkForMainThreadException();
             assertTrue("on stop should be called", calledOnStop.await(30, TimeUnit.SECONDS));
             checkForMainThreadException();
             assertNotNull("should scroll to new target " + newTarget
-                    , rv.findViewHolderForPosition(newTarget));
+                    , rv.findViewHolderForLayoutPosition(newTarget));
             if (DEBUG) {
                 Log.d(TAG, "on stop has been called on time");
             }
@@ -443,11 +1006,66 @@
             assertTrue("on stop should be called eventually",
                     calledOnStop.await(30, TimeUnit.SECONDS));
             assertNotNull("scroll to position should succeed",
-                    rv.findViewHolderForPosition(targetPosition));
+                    rv.findViewHolderForLayoutPosition(targetPosition));
         }
         checkForMainThreadException();
     }
 
+    public void testConsecutiveSmoothScroll() throws Throwable {
+        final AtomicInteger visibleChildCount = new AtomicInteger(10);
+        final AtomicInteger totalScrolled = new AtomicInteger(0);
+        final TestLayoutManager lm = new TestLayoutManager() {
+            int start = 0;
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutRange(recycler, start, visibleChildCount.get());
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                totalScrolled.set(totalScrolled.get() + dy);
+                return dy;
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+        };
+        final RecyclerView rv = new RecyclerView(getActivity());
+        TestAdapter testAdapter = new TestAdapter(500);
+        rv.setLayoutManager(lm);
+        rv.setAdapter(testAdapter);
+        lm.expectLayouts(1);
+        setRecyclerView(rv);
+        lm.waitForLayout(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                rv.smoothScrollBy(0, 2000);
+            }
+        });
+        Thread.sleep(250);
+        final AtomicInteger scrollAmt = new AtomicInteger();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final int soFar = totalScrolled.get();
+                scrollAmt.set(soFar);
+                rv.smoothScrollBy(0, 5000 - soFar);
+            }
+        });
+        while (rv.getScrollState() != SCROLL_STATE_IDLE) {
+            Thread.sleep(100);
+        }
+        final int soFar = totalScrolled.get();
+        assertEquals("second scroll should be competed properly", 5000, soFar);
+    }
+
     public void accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)
             throws Throwable {
         TestAdapter testAdapter = new TestAdapter(10);
@@ -478,7 +1096,7 @@
                     }
                     assertEquals(state.toString(),
                             expectedOnMeasureStateCount.get(), state.getItemCount());
-                } catch(Throwable t) {
+                } catch (Throwable t) {
                     postExceptionToInstrumentation(t);
                 }
                 super.onMeasure(recycler, state, widthSpec, heightSpec);
@@ -524,7 +1142,6 @@
         TestLayoutManager lm = new TestLayoutManager() {
             @Override
             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
-                super.onLayoutChildren(recycler, state);
                 try {
                     layoutRange(recycler, 0, state.getItemCount());
                     layoutLatch.countDown();
@@ -538,7 +1155,6 @@
         RecyclerView recyclerView = new RecyclerView(getActivity());
         recyclerView.setLayoutManager(lm);
         recyclerView.setAdapter(testAdapter);
-        recyclerView.setLayoutManager(lm);
         recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
             @Override
             public void onViewRecycled(RecyclerView.ViewHolder holder) {
@@ -689,7 +1305,7 @@
             public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
                 try {
                     // test
-                    for (int i = 0; i < getChildCount(); i ++) {
+                    for (int i = 0; i < getChildCount(); i++) {
                         View child = getChildAt(i);
                         RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
                                 child.getLayoutParams();
@@ -723,7 +1339,7 @@
         recyclerView.setItemViewCacheSize(5);
         recyclerView.setLayoutManager(testLayoutManager);
         testLayoutManager.expectLayouts(1);
-        setRecyclerView(recyclerView);
+        setRecyclerView(recyclerView, true, false);
         testLayoutManager.waitForLayout(2);
         checkForMainThreadException();
 
@@ -735,6 +1351,7 @@
         checkForMainThreadException();
 
         // invalidate w/o an item decorator
+
         invalidateDecorOffsets(recyclerView);
         testLayoutManager.expectLayouts(1);
         invalidateDecorOffsets(recyclerView);
@@ -788,7 +1405,7 @@
     }
 
     public void addItemDecoration(final RecyclerView recyclerView, final
-            RecyclerView.ItemDecoration itemDecoration) throws Throwable {
+    RecyclerView.ItemDecoration itemDecoration) throws Throwable {
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -830,7 +1447,7 @@
                 try {
                     if (changes.size() > 0) {
                         // test
-                        for (int i = 0; i < getChildCount(); i ++) {
+                        for (int i = 0; i < getChildCount(); i++) {
                             View child = getChildAt(i);
                             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
                                     child.getLayoutParams();
@@ -864,10 +1481,10 @@
         testLayoutManager.waitForLayout(2);
         int itemAddedTo = 5;
         for (int i = 0; i < itemAddedTo; i++) {
-            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+            changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
         }
         for (int i = itemAddedTo; i < mRecyclerView.getChildCount(); i++) {
-            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+            changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
         }
         testLayoutManager.expectLayouts(1);
         adapter.addAndNotify(5, 1);
@@ -876,19 +1493,20 @@
 
         changes.clear();
         int[] changedItems = new int[]{3, 5, 6};
-        for (int i = 0; i < adapter.getItemCount(); i ++) {
-            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+        for (int i = 0; i < adapter.getItemCount(); i++) {
+            changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), false);
         }
-        for (int i = 0; i < changedItems.length; i ++) {
-            changes.put(mRecyclerView.findViewHolderForPosition(changedItems[i]).getItemId(), true);
+        for (int i = 0; i < changedItems.length; i++) {
+            changes.put(mRecyclerView.findViewHolderForLayoutPosition(changedItems[i]).getItemId(),
+                    true);
         }
         testLayoutManager.expectLayouts(1);
         adapter.changePositionsAndNotify(changedItems);
         testLayoutManager.waitForLayout(2);
         checkForMainThreadException();
 
-        for (int i = 0; i < adapter.getItemCount(); i ++) {
-            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+        for (int i = 0; i < adapter.getItemCount(); i++) {
+            changes.put(mRecyclerView.findViewHolderForLayoutPosition(i).getItemId(), true);
         }
         testLayoutManager.expectLayouts(1);
         adapter.dispatchDataSetChanged();
@@ -1214,10 +1832,11 @@
                     adapter.addAndNotify(4, 5);
                     removeRecyclerView();
                 } catch (Throwable throwable) {
-                    throwable.printStackTrace();
+                    postExceptionToInstrumentation(throwable);
                 }
             }
         });
+        checkForMainThreadException();
 
         lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
         assertEquals("No extra layout should happen when detached", prevLayoutCount,
@@ -1283,10 +1902,10 @@
                             View view = getChildAt(i);
                             TestViewHolder tvh = (TestViewHolder) mRecyclerView
                                     .getChildViewHolder(view);
-                            final int oldPos = previousItems.indexOf(tvh.mBindedItem);
+                            final int oldPos = previousItems.indexOf(tvh.mBoundItem);
                             assertEquals("view holder's position should be correct",
                                     oldPositionToNewPositionMapping.get(oldPos).intValue(),
-                                    tvh.getPosition());
+                                    tvh.getLayoutPosition());
                             ;
                         }
                     }
@@ -1335,6 +1954,92 @@
         checkForMainThreadException();
     }
 
+    public void testCallbacksDuringAdapterSwap() throws Throwable {
+        callbacksDuringAdapterChange(true);
+    }
+
+    public void testCallbacksDuringAdapterSet() throws Throwable {
+        callbacksDuringAdapterChange(false);
+    }
+
+    public void callbacksDuringAdapterChange(boolean swap) throws Throwable {
+        final TestAdapter2 adapter1 = swap ? createBinderCheckingAdapter()
+                : createOwnerCheckingAdapter();
+        final TestAdapter2 adapter2 = swap ? createBinderCheckingAdapter()
+                : createOwnerCheckingAdapter();
+
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    layoutRange(recycler, 0, state.getItemCount());
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+                layoutLatch.countDown();
+            }
+        };
+        RecyclerView rv = new RecyclerView(getActivity());
+        rv.setAdapter(adapter1);
+        rv.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(rv);
+        tlm.waitForLayout(1);
+        checkForMainThreadException();
+        tlm.expectLayouts(1);
+        if (swap) {
+            swapAdapter(adapter2, true);
+        } else {
+            setAdapter(adapter2);
+        }
+        checkForMainThreadException();
+        tlm.waitForLayout(1);
+        checkForMainThreadException();
+    }
+
+    private TestAdapter2 createOwnerCheckingAdapter() {
+        return new TestAdapter2(10) {
+            @Override
+            public void onViewRecycled(TestViewHolder2 holder) {
+                assertSame("on recycled should be called w/ the creator adapter", this,
+                        holder.mData);
+                super.onViewRecycled(holder);
+            }
+
+            @Override
+            public void onBindViewHolder(TestViewHolder2 holder, int position) {
+                super.onBindViewHolder(holder, position);
+                assertSame("on bind should be called w/ the creator adapter", this, holder.mData);
+            }
+
+            @Override
+            public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                final TestViewHolder2 vh = super.onCreateViewHolder(parent, viewType);
+                vh.mData = this;
+                return vh;
+            }
+        };
+    }
+
+    private TestAdapter2 createBinderCheckingAdapter() {
+        return new TestAdapter2(10) {
+            @Override
+            public void onViewRecycled(TestViewHolder2 holder) {
+                assertSame("on recycled should be called w/ the creator adapter", this,
+                        holder.mData);
+                holder.mData = null;
+                super.onViewRecycled(holder);
+            }
+
+            @Override
+            public void onBindViewHolder(TestViewHolder2 holder, int position) {
+                super.onBindViewHolder(holder, position);
+                holder.mData = this;
+            }
+        };
+    }
+
     public void testFindViewById() throws Throwable {
         findViewByIdTest(false);
         removeRecyclerView();
@@ -1386,7 +2091,7 @@
                             TestViewHolder viewHolder =
                                     (TestViewHolder) mRecyclerView.getChildViewHolder(view);
                             assertSame("should be the correct item " + viewHolder
-                                    , viewHolder.mBindedItem,
+                                    , viewHolder.mBoundItem,
                                     adapter.mItems.get(viewHolder.mPosition));
                             assertFalse("view should not be marked as removed",
                                     ((RecyclerView.LayoutParams) view.getLayoutParams())
@@ -1465,7 +2170,7 @@
             @Override
             public void run() {
                 for (int i = 2; i < 4; i++) {
-                    RecyclerView.ViewHolder vh = recyclerView.findViewHolderForPosition(i);
+                    RecyclerView.ViewHolder vh = recyclerView.findViewHolderForLayoutPosition(i);
                     assertEquals("View holder's type should match latest type", viewType.get(),
                             vh.getItemViewType());
                 }
@@ -1590,13 +2295,170 @@
                 structureChanged.get());
     }
 
+    public void testDetachWithoutLayoutManager() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setRecyclerView(recyclerView);
+                    removeRecyclerView();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+            }
+        });
+        checkForMainThreadException();
+    }
+
+    public void testUpdateHiddenView() throws Throwable {
+        final RecyclerView.ViewHolder[] mTargetVH = new RecyclerView.ViewHolder[1];
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final int[] preLayoutRange = new int[]{0, 10};
+        final int[] postLayoutRange = new int[]{0, 10};
+        final AtomicBoolean enableGetViewTest = new AtomicBoolean(false);
+        final List<Integer> disappearingPositions = new ArrayList<Integer>();
+        final TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return true;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    final int[] layoutRange = state.isPreLayout() ? preLayoutRange
+                            : postLayoutRange;
+                    detachAndScrapAttachedViews(recycler);
+                    layoutRange(recycler, layoutRange[0], layoutRange[1]);
+                    if (!state.isPreLayout()) {
+                        for (Integer position : disappearingPositions) {
+                            // test sanity.
+                            assertNull(findViewByPosition(position));
+                            final View view = recycler.getViewForPosition(position);
+                            addDisappearingView(view);
+                            measureChildWithMargins(view, 0, 0);
+                            // position item out of bounds.
+                            view.layout(0, -500, view.getMeasuredWidth(),
+                                    -500 + view.getMeasuredHeight());
+                        }
+                    }
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+                layoutLatch.countDown();
+            }
+        };
+
+        recyclerView.getItemAnimator().setMoveDuration(2000);
+        recyclerView.getItemAnimator().setRemoveDuration(2000);
+        final TestAdapter adapter = new TestAdapter(100);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(tlm);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+
+        tlm.waitForLayout(1);
+        checkForMainThreadException();
+        mTargetVH[0] = recyclerView.findViewHolderForAdapterPosition(0);
+        // now, a child disappears
+        disappearingPositions.add(0);
+        // layout one shifted
+        postLayoutRange[0] = 1;
+        postLayoutRange[1] = 11;
+        tlm.expectLayouts(2);
+        adapter.addAndNotify(8, 1);
+        tlm.waitForLayout(2);
+        checkForMainThreadException();
+
+        tlm.expectLayouts(2);
+        disappearingPositions.clear();
+        // now that item should be moving, invalidate it and delete it.
+        enableGetViewTest.set(true);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    adapter.changeAndNotify(0, 1);
+                    adapter.deleteAndNotify(0, 1);
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+            }
+        });
+        tlm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testFocusRectOnScreenWithDecorOffsets() throws Throwable {
+        focusRectOnScreenTest(true);
+    }
+
+    public void testFocusRectOnScreenWithout() throws Throwable {
+        focusRectOnScreenTest(false);
+    }
+
+
+    public void focusRectOnScreenTest(boolean addItemDecors) throws Throwable {
+        RecyclerView rv = new RecyclerView(getActivity());
+        final AtomicInteger scrollDist = new AtomicInteger(0);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                final View view = recycler.getViewForPosition(0);
+                addView(view);
+                measureChildWithMargins(view, 0, 0);
+                view.layout(0, -20, view.getWidth(),
+                        -20 + view.getHeight());// ignore decors on purpose
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                scrollDist.addAndGet(dy);
+                return dy;
+            }
+        };
+        TestAdapter adapter = new TestAdapter(10);
+        if (addItemDecors) {
+            rv.addItemDecoration(new RecyclerView.ItemDecoration() {
+                @Override
+                public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                        RecyclerView.State state) {
+                    outRect.set(0, 10, 0, 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(addItemDecors ? -30 : -20, scrollDist.get());
+    }
+
     private static class TestViewHolder2 extends RecyclerView.ViewHolder {
+
+        Object mData;
+
         public TestViewHolder2(View itemView) {
             super(itemView);
         }
     }
 
     private static class TestAdapter2 extends RecyclerView.Adapter<TestViewHolder2> {
+
         List<Item> mItems;
 
         private TestAdapter2(int count) {
@@ -1624,4 +2486,9 @@
         }
     }
 
+    private static interface AdapterRunnable {
+
+        public void run(TestAdapter adapter) throws Throwable;
+    }
+
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
index bc80da0..e23a114 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -19,6 +19,7 @@
 
 
 import android.graphics.Rect;
+import android.os.Debug;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -84,6 +86,140 @@
         mLayoutManager.setGapStrategy(config.mGapStrategy);
         mLayoutManager.setReverseLayout(config.mReverseLayout);
         mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+            @Override
+            public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                    RecyclerView.State state) {
+                try {
+                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                    assertNotNull("view should have layout params assigned", lp);
+                    assertNotNull("when item offsets are requested, view should have a valid span",
+                            lp.mSpan);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+            }
+        });
+    }
+
+    public void testAreAllStartsTheSame() throws Throwable {
+        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300));
+        waitFirstLayout();
+        smoothScrollToPosition(100);
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(0, 2);
+        mLayoutManager.waitForLayout(2);
+        smoothScrollToPosition(0);
+        assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual());
+    }
+
+    public void testAreAllEndsTheSame() throws Throwable {
+        setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300));
+        waitFirstLayout();
+        smoothScrollToPosition(100);
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(0, 2);
+        mLayoutManager.waitForLayout(2);
+        smoothScrollToPosition(0);
+        assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual());
+    }
+
+    public void testFindLastInUnevenDistribution() throws Throwable {
+        setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
+                .itemCount(5));
+        mAdapter.mOnBindHandler = new OnBindHandler() {
+            @Override
+            void onBoundItem(TestViewHolder vh, int position) {
+                LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
+                if (position == 1) {
+                    lp.height = mRecyclerView.getHeight() - 10;
+                } else {
+                    lp.height = 5;
+                }
+            }
+        };
+        waitFirstLayout();
+        int[] into = new int[2];
+        mLayoutManager.findFirstCompletelyVisibleItemPositions(into);
+        assertEquals("first completely visible item from span 0 should be 0", 0, into[0]);
+        assertEquals("first completely visible item from span 1 should be 1", 1, into[1]);
+        mLayoutManager.findLastCompletelyVisibleItemPositions(into);
+        assertEquals("last completely visible item from span 0 should be 4", 4, into[0]);
+        assertEquals("last completely visible item from span 1 should be 1", 1, into[1]);
+        assertEquals("first fully visible child should be at position",
+                0, mRecyclerView.getChildViewHolder(mLayoutManager.
+                        findFirstVisibleItemClosestToStart(true, true)).getPosition());
+        assertEquals("last fully visible child should be at position",
+                4, mRecyclerView.getChildViewHolder(mLayoutManager.
+                        findFirstVisibleItemClosestToEnd(true, true)).getPosition());
+
+        assertEquals("first visible child should be at position",
+                0, mRecyclerView.getChildViewHolder(mLayoutManager.
+                        findFirstVisibleItemClosestToStart(false, true)).getPosition());
+        assertEquals("last visible child should be at position",
+                4, mRecyclerView.getChildViewHolder(mLayoutManager.
+                        findFirstVisibleItemClosestToEnd(false, true)).getPosition());
+
+    }
+
+    public void testCustomWidthInHorizontal() throws Throwable {
+        customSizeInScrollDirectionTest(
+                new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+    }
+
+    public void testCustomHeightInVertical() throws Throwable {
+        customSizeInScrollDirectionTest(
+                new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+    }
+
+    public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
+        setupByConfig(config);
+        final Map<View, Integer> sizeMap = new HashMap<View, Integer>();
+        mAdapter.mOnBindHandler = new OnBindHandler() {
+            @Override
+            void onBoundItem(TestViewHolder vh, int position) {
+                final ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams();
+                final int size = 1 + position * 5;
+                if (config.mOrientation == HORIZONTAL) {
+                    layoutParams.width = size;
+                } else {
+                    layoutParams.height = size;
+                }
+                sizeMap.put(vh.itemView, size);
+                if (position == 3) {
+                    getLp(vh.itemView).setFullSpan(true);
+                }
+            }
+
+            @Override
+            boolean assignRandomSize() {
+                return false;
+            }
+        };
+        waitFirstLayout();
+        assertTrue("[test sanity] some views should be laid out", sizeMap.size() > 0);
+        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+            View child = mRecyclerView.getChildAt(i);
+            final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
+                    : child.getHeight();
+            assertEquals("child " + i + " should have the size specified in its layout params",
+                    sizeMap.get(child).intValue(), size);
+        }
+        checkForMainThreadException();
+    }
+
+    public void testGrowLookup() throws Throwable {
+        setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+        waitFirstLayout();
+        mLayoutManager.expectLayouts(1);
+        mAdapter.mItems.clear();
+        mAdapter.dispatchDataSetChanged();
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+        mLayoutManager.expectLayouts(2);
+        mAdapter.addAndNotify(0, 30);
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
     }
 
     public void testRTL() throws Throwable {
@@ -115,7 +251,7 @@
         OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
         View child0 = mLayoutManager.findViewByPosition(0);
         View child1 = mLayoutManager.findViewByPosition(config.mOrientation == VERTICAL ? 1
-            : config.mSpanCount);
+                : config.mSpanCount);
         assertNotNull(logPrefix + " child position 0 should be laid out", child0);
         assertNotNull(logPrefix + " child position 0 should be laid out", child1);
         if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
@@ -141,14 +277,15 @@
         }
     }
 
-    public void scrollBackAndPreservePositionsTest(final Config config, final boolean saveRestoreInBetween)
+    public void scrollBackAndPreservePositionsTest(final Config config,
+            final boolean saveRestoreInBetween)
             throws Throwable {
         setupByConfig(config);
         mAdapter.mOnBindHandler = new OnBindHandler() {
             @Override
-            public void onBoundItem(TestViewHolder vh, int postion) {
+            public void onBoundItem(TestViewHolder vh, int position) {
                 LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
-                lp.setFullSpan((postion * 7) % (config.mSpanCount + 1) == 0);
+                lp.setFullSpan((position * 7) % (config.mSpanCount + 1) == 0);
             }
         };
         waitFirstLayout();
@@ -157,7 +294,6 @@
         final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
                 * (config.mReverseLayout ? -1 : 1);
 
-
         final int[] globalPos = new int[1];
         runTestOnUiThread(new Runnable() {
             @Override
@@ -166,7 +302,7 @@
                 while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
                     for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
                         View child = mRecyclerView.getChildAt(i);
-                        final int pos = mRecyclerView.getChildPosition(child);
+                        final int pos = mRecyclerView.getChildLayoutPosition(child);
                         if (globalPositions[pos] != Integer.MIN_VALUE) {
                             continue;
                         }
@@ -207,7 +343,7 @@
                 while (!shouldTest.isEmpty() && scrollAmount != 0) {
                     for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
                         View child = mRecyclerView.getChildAt(i);
-                        int pos = mRecyclerView.getChildPosition(child);
+                        int pos = mRecyclerView.getChildLayoutPosition(child);
                         if (!shouldTest.get(pos)) {
                             continue;
                         }
@@ -262,13 +398,13 @@
                                 scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
                     }
                 } else {
-                    RecyclerView.ViewHolder vh = rv.findViewHolderForPosition(scrollPosition);
+                    RecyclerView.ViewHolder vh = rv.findViewHolderForLayoutPosition(scrollPosition);
                     assertNotNull("scroll to position should work", vh);
                     if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
                         assertEquals("scroll offset should be applied properly",
                                 mLayoutManager.getPaddingTop() + scrollOffset
                                         + ((RecyclerView.LayoutParams) vh.itemView
-                                            .getLayoutParams()).topMargin,
+                                        .getLayoutParams()).topMargin,
                                 mLayoutManager.getDecoratedTop(vh.itemView));
                     }
                 }
@@ -352,6 +488,7 @@
         while (mLayoutManager.isSmoothScrolling() ||
                 mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
             runTestOnUiThread(viewInBoundsTest);
+            checkForMainThreadException();
             Thread.sleep(400);
         }
         // delete all items
@@ -383,6 +520,84 @@
         });
         mLayoutManager.waitForLayout(2);
         runTestOnUiThread(viewInBoundsTest);
+        checkForMainThreadException();
+    }
+
+    public void testMoveGapHandling() throws Throwable {
+        Config config = new Config().spanCount(2).itemCount(40);
+        setupByConfig(config);
+        waitFirstLayout();
+        mLayoutManager.expectLayouts(2);
+        mAdapter.moveAndNotify(4, 1);
+        mLayoutManager.waitForLayout(2);
+        assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix());
+    }
+
+    public void testUpdateAfterFullSpan() throws Throwable {
+        updateAfterFullSpanGapHandlingTest(0);
+    }
+
+    public void testUpdateAfterFullSpan2() throws Throwable {
+        updateAfterFullSpanGapHandlingTest(20);
+    }
+
+    public void testTemporaryGapHandling() throws Throwable {
+        int fullSpanIndex = 200;
+        setupByConfig(new Config().spanCount(2).itemCount(500));
+        mAdapter.mFullSpanItems.add(fullSpanIndex);
+        waitFirstLayout();
+        smoothScrollToPosition(fullSpanIndex + 30);
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
+        mLayoutManager.waitForLayout(1);
+        smoothScrollToPosition(0);
+        mLayoutManager.expectLayouts(1);
+        smoothScrollToPosition(fullSpanIndex + 5);
+        mLayoutManager.assertNoLayout("if an interim gap is fixed, it should not cause a "
+                + "relayout", 2);
+        View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
+
+        View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
+        View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
+
+        LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
+        LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
+        assertEquals("view 1 span index", 0, lp1.getSpanIndex());
+        assertEquals("view 2 span index", 1, lp2.getSpanIndex());
+        assertEquals("no gap between span and view 1",
+                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
+        assertEquals("no gap between span and view 2",
+                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
+    }
+
+    public void updateAfterFullSpanGapHandlingTest(int fullSpanIndex) throws Throwable {
+        setupByConfig(new Config().spanCount(2).itemCount(100));
+        mAdapter.mFullSpanItems.add(fullSpanIndex);
+        waitFirstLayout();
+        smoothScrollToPosition(fullSpanIndex + 30);
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(fullSpanIndex + 1, 3);
+        mLayoutManager.waitForLayout(1);
+        smoothScrollToPosition(fullSpanIndex);
+        // give it some time to fix the gap
+        Thread.sleep(500);
+        View fullSpan = mLayoutManager.findViewByPosition(fullSpanIndex);
+
+        View view1 = mLayoutManager.findViewByPosition(fullSpanIndex + 1);
+        View view2 = mLayoutManager.findViewByPosition(fullSpanIndex + 2);
+
+        LayoutParams lp1 = (LayoutParams) view1.getLayoutParams();
+        LayoutParams lp2 = (LayoutParams) view2.getLayoutParams();
+        assertEquals("view 1 span index", 0, lp1.getSpanIndex());
+        assertEquals("view 2 span index", 1, lp2.getSpanIndex());
+        assertEquals("no gap between span and view 1",
+                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view1));
+        assertEquals("no gap between span and view 2",
+                mLayoutManager.mPrimaryOrientation.getDecoratedEnd(fullSpan),
+                mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
     }
 
     public void testInnerGapHandling() throws Throwable {
@@ -454,9 +669,9 @@
 
     public void testGapAtTheBeginning() throws Throwable {
         for (Config config : mBaseVariations) {
-            for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount ++) {
+            for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
                 for (int deletePosition = config.mSpanCount - 1;
-                        deletePosition < config.mSpanCount + 2; deletePosition ++) {
+                        deletePosition < config.mSpanCount + 2; deletePosition++) {
                     gapAtTheBeginningOfTheListTest(config, deletePosition, deleteCount);
                     removeRecyclerView();
                 }
@@ -481,7 +696,7 @@
         smoothScrollToPosition(config.mItemCount / 2);
         // assert to be deleted child is not visible
         assertNull(logPrefix + " test sanity, to be deleted child should be invisible",
-                mRecyclerView.findViewHolderForPosition(deletePosition));
+                mRecyclerView.findViewHolderForLayoutPosition(deletePosition));
         // delete the child and notify
         mAdapter.deleteAndNotify(deletePosition, deleteCount);
         getInstrumentation().waitForIdleSync();
@@ -537,8 +752,9 @@
     // Same as Arrays.copyOfRange but for API 7
     private int[] copyOfRange(int[] original, int from, int to) {
         int newLength = to - from;
-        if (newLength < 0)
+        if (newLength < 0) {
             throw new IllegalArgumentException(from + " > " + to);
+        }
         int[] copy = new int[newLength];
         System.arraycopy(original, from, copy, 0,
                 Math.min(original.length - from, newLength));
@@ -779,33 +995,44 @@
         }
     }
 
-    private void saveRestore(Config config) throws Throwable {
-        Parcelable savedState = mRecyclerView.onSaveInstanceState();
-        // we append a suffix to the parcelable to test out of bounds
-        String parcelSuffix = UUID.randomUUID().toString();
-        Parcel parcel = Parcel.obtain();
-        savedState.writeToParcel(parcel, 0);
-        parcel.writeString(parcelSuffix);
-        removeRecyclerView();
-        // reset for reading
-        parcel.setDataPosition(0);
-        // re-create
-        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
-        RecyclerView restored = new RecyclerView(getActivity());
-        mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
-        mLayoutManager.setGapStrategy(config.mGapStrategy);
-        restored.setLayoutManager(mLayoutManager);
-        // use the same adapter for Rect matching
-        restored.setAdapter(mAdapter);
-        restored.onRestoreInstanceState(savedState);
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            mLayoutManager.expectLayouts(1);
-            setRecyclerView(restored);
-        } else {
-            mLayoutManager.expectLayouts(1);
-            setRecyclerView(restored);
-            mLayoutManager.waitForLayout(2);
-        }
+    private void saveRestore(final Config config) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Parcelable savedState = mRecyclerView.onSaveInstanceState();
+                    // we append a suffix to the parcelable to test out of bounds
+                    String parcelSuffix = UUID.randomUUID().toString();
+                    Parcel parcel = Parcel.obtain();
+                    savedState.writeToParcel(parcel, 0);
+                    parcel.writeString(parcelSuffix);
+                    removeRecyclerView();
+                    // reset for reading
+                    parcel.setDataPosition(0);
+                    // re-create
+                    savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+                    RecyclerView restored = new RecyclerView(getActivity());
+                    mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
+                            config.mOrientation);
+                    mLayoutManager.setGapStrategy(config.mGapStrategy);
+                    restored.setLayoutManager(mLayoutManager);
+                    // use the same adapter for Rect matching
+                    restored.setAdapter(mAdapter);
+                    restored.onRestoreInstanceState(savedState);
+                    if (Looper.myLooper() == Looper.getMainLooper()) {
+                        mLayoutManager.expectLayouts(1);
+                        setRecyclerView(restored);
+                    } else {
+                        mLayoutManager.expectLayouts(1);
+                        setRecyclerView(restored);
+                        mLayoutManager.waitForLayout(2);
+                    }
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+            }
+        });
+        checkForMainThreadException();
     }
 
     public void savedStateTest(Config config, boolean waitForLayout,
@@ -856,7 +1083,7 @@
         assertEquals(config + " on saved state, gap strategy should be preserved",
                 config.mGapStrategy, mLayoutManager.getGapStrategy());
         assertEquals(config + " on saved state, first completely visible child position should"
-                + " be preserved", firstCompletelyVisiblePosition,
+                        + " be preserved", firstCompletelyVisiblePosition,
                 mLayoutManager.findFirstVisibleItemPositionInt());
         if (waitForLayout) {
             assertRectSetsEqual(config + "\npost layout op:" + postLayoutOperations.describe()
@@ -890,7 +1117,7 @@
         while (testCount-- > 0) {
             // get middle child
             final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
-            final int position = mRecyclerView.getChildPosition(child);
+            final int position = mRecyclerView.getChildLayoutPosition(child);
             final int startOffset = config.mReverseLayout ?
                     orientationHelper.getEndAfterPadding() - orientationHelper
                             .getDecoratedEnd(child)
@@ -958,7 +1185,7 @@
         int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
         for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
             View child = mLayoutManager.getChildAt(i);
-            int position = mRecyclerView.getChildPosition(child);
+            int position = mRecyclerView.getChildLayoutPosition(child);
             if (position < minPosition) {
                 minPosition = position;
             }
@@ -996,12 +1223,12 @@
             Rect bounds = mLayoutManager.getViewBounds(view);
             if (layoutBounds.contains(bounds)) {
                 Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
-                final int position = mRecyclerView.getChildPosition(view);
+                final int position = mRecyclerView.getChildLayoutPosition(view);
                 LayoutParams layoutParams
                         = (LayoutParams) (view.getLayoutParams());
                 TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
                 assertEquals("recycler view mPosition should match adapter mPosition", position,
-                        vh.mBindedItem.mAdapterIndex);
+                        vh.mBoundItem.mAdapterIndex);
                 if (DEBUG) {
                     Log.d(TAG, "testing scroll to visible mPosition at " + position
                             + " " + bounds + " inside " + layoutBounds);
@@ -1020,12 +1247,12 @@
                         config + "scroll to mPosition on fully visible child should be no-op",
                         initialBounds, mLayoutManager.collectChildCoordinates());
             } else {
-                final int position = mRecyclerView.getChildPosition(view);
+                final int position = mRecyclerView.getChildLayoutPosition(view);
                 if (DEBUG) {
                     Log.d(TAG,
                             "child(" + position + ") not fully visible " + bounds + " not inside "
                                     + layoutBounds
-                                    + mRecyclerView.getChildPosition(view)
+                                    + mRecyclerView.getChildLayoutPosition(view)
                     );
                 }
                 mLayoutManager.expectLayouts(1);
@@ -1220,9 +1447,11 @@
         final AccessibilityRecordCompat record = AccessibilityEventCompat
                 .asRecord(event);
         final int start = mRecyclerView
-                .getChildPosition(mLayoutManager.findFirstVisibleItemClosestToStart(false));
+                .getChildLayoutPosition(
+                        mLayoutManager.findFirstVisibleItemClosestToStart(false, true));
         final int end = mRecyclerView
-                .getChildPosition(mLayoutManager.findFirstVisibleItemClosestToEnd(false));
+                .getChildLayoutPosition(
+                        mLayoutManager.findFirstVisibleItemClosestToEnd(false, true));
         assertEquals("first item position should match",
                 Math.min(start, end), record.getFromIndex());
         assertEquals("last item position should match",
@@ -1332,8 +1561,12 @@
     // test layout params assignment
 
     static class OnLayoutListener {
-        void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
-        void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
+
+        void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        }
+
+        void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        }
     }
 
     class WrappedLayoutManager extends StaggeredGridLayoutManager {
@@ -1495,7 +1728,7 @@
                         LayoutParams lp = (LayoutParams) child
                                 .getLayoutParams();
                         TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
-                        items.put(vh.mBindedItem, getViewBounds(child));
+                        items.put(vh.mBoundItem, getViewBounds(child));
                     }
                 }
             });
@@ -1626,12 +1859,6 @@
                 int position) {
             super.onBindViewHolder(holder, position);
             Item item = mItems.get(position);
-            final int minSize = mViewsHaveEqualSize ? 200 : 200 + 20 * (position % 10);
-            if (mOrientation == OrientationHelper.HORIZONTAL) {
-                holder.itemView.setMinimumWidth(minSize);
-            } else {
-                holder.itemView.setMinimumHeight(minSize);
-            }
             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
                     .getLayoutParams();
             if (lp instanceof LayoutParams) {
@@ -1643,10 +1870,19 @@
                 slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
                 lp = slp;
             }
-            lp.topMargin = 3;
-            lp.leftMargin = 5;
-            lp.rightMargin = 7;
-            lp.bottomMargin = 9;
+
+            if (mOnBindHandler == null || mOnBindHandler.assignRandomSize()) {
+                final int minSize = mViewsHaveEqualSize ? 200 : 200 + 20 * (position % 10);
+                if (mOrientation == OrientationHelper.HORIZONTAL) {
+                    holder.itemView.setMinimumWidth(minSize);
+                } else {
+                    holder.itemView.setMinimumHeight(minSize);
+                }
+                lp.topMargin = 3;
+                lp.leftMargin = 5;
+                lp.rightMargin = 7;
+                lp.bottomMargin = 9;
+            }
 
             if (mOnBindHandler != null) {
                 mOnBindHandler.onBoundItem(holder, position);
@@ -1654,8 +1890,13 @@
         }
     }
 
-    static interface OnBindHandler {
-        void onBoundItem(TestViewHolder vh, int postion);
+    abstract static class OnBindHandler {
+
+        abstract void onBoundItem(TestViewHolder vh, int position);
+
+        boolean assignRandomSize() {
+            return true;
+        }
     }
 
     static class Config implements Cloneable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
index a0b165c..a4cb473 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 
 public class TestActivity extends Activity {
@@ -28,6 +29,8 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mContainer = new FrameLayout(this);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         setContentView(mContainer);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 }
diff --git a/v7/vectordrawable/Android.mk b/v7/vectordrawable/Android.mk
new file mode 100644
index 0000000..c42b209
--- /dev/null
+++ b/v7/vectordrawable/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-vectordrawable
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/vectordrawable/runtest.sh b/v7/vectordrawable/runtest.sh
new file mode 100755
index 0000000..f19a537
--- /dev/null
+++ b/v7/vectordrawable/runtest.sh
@@ -0,0 +1,5 @@
+. ../../../../build/envsetup.sh
+mmm -j20 . && mmm -j20 ./tests/ && \
+adb install -r $OUT/data/app/AndroidVectorDrawableTests/AndroidVectorDrawableTests.apk && \
+adb shell am start -n android.support.v7.vectordrawable/android.support.v7.vectordrawable.TestActivity
+
diff --git a/v7/vectordrawable/src/android/support/v7/graphics/drawable/PathParser.java b/v7/vectordrawable/src/android/support/v7/graphics/drawable/PathParser.java
new file mode 100644
index 0000000..2f6b9d1
--- /dev/null
+++ b/v7/vectordrawable/src/android/support/v7/graphics/drawable/PathParser.java
@@ -0,0 +1,718 @@
+/*
+ * 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.v7.graphics.drawable;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+// This class is a duplicate from the PathParser.java of frameworks/base, with slight
+// update on incompatible API like copyOfRange().
+class PathParser {
+    private static final String LOGTAG = "PathParser";
+
+    // Copy from Arrays.copyOfRange() which is only available from API level 9.
+    /**
+     * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
+     * end (exclusive). The original order of elements is preserved.
+     * If {@code end} is greater than {@code original.length}, the result is padded
+     * with the value {@code 0.0f}.
+     *
+     * @param original the original array
+     * @param start the start index, inclusive
+     * @param end the end index, exclusive
+     * @return the new array
+     * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
+     * @throws IllegalArgumentException if {@code start > end}
+     * @throws NullPointerException if {@code original == null}
+     */
+    private static float[] copyOfRange(float[] original, int start, int end) {
+        if (start > end) {
+            throw new IllegalArgumentException();
+        }
+        int originalLength = original.length;
+        if (start < 0 || start > originalLength) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int resultLength = end - start;
+        int copyLength = Math.min(resultLength, originalLength - start);
+        float[] result = new float[resultLength];
+        System.arraycopy(original, start, result, 0, copyLength);
+        return result;
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return the generated Path object.
+     */
+    public static Path createPathFromPathData(String pathData) {
+        Path path = new Path();
+        PathDataNode[] nodes = createNodesFromPathData(pathData);
+        if (nodes != null) {
+            try {
+                PathDataNode.nodesToPath(nodes, path);
+            } catch (RuntimeException e) {
+                throw new RuntimeException("Error in parsing " + pathData, e);
+            }
+            return path;
+        }
+        return null;
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     * @return an array of the PathDataNode.
+     */
+    public static PathDataNode[] createNodesFromPathData(String pathData) {
+        if (pathData == null) {
+            return null;
+        }
+        int start = 0;
+        int end = 1;
+
+        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+        while (end < pathData.length()) {
+            end = nextStart(pathData, end);
+            String s = pathData.substring(start, end).trim();
+            if (s.length() > 0) {
+                float[] val = getFloats(s);
+                addNode(list, s.charAt(0), val);
+            }
+
+            start = end;
+            end++;
+        }
+        if ((end - start) == 1 && start < pathData.length()) {
+            addNode(list, pathData.charAt(start), new float[0]);
+        }
+        return list.toArray(new PathDataNode[list.size()]);
+    }
+
+    /**
+     * @param source The array of PathDataNode to be duplicated.
+     * @return a deep copy of the <code>source</code>.
+     */
+    public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
+        if (source == null) {
+            return null;
+        }
+        PathDataNode[] copy = new PathParser.PathDataNode[source.length];
+        for (int i = 0; i < source.length; i ++) {
+            copy[i] = new PathDataNode(source[i]);
+        }
+        return copy;
+    }
+
+    /**
+     * @param nodesFrom The source path represented in an array of PathDataNode
+     * @param nodesTo The target path represented in an array of PathDataNode
+     * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
+     */
+    public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
+        if (nodesFrom == null || nodesTo == null) {
+            return false;
+        }
+
+        if (nodesFrom.length != nodesTo.length) {
+            return false;
+        }
+
+        for (int i = 0; i < nodesFrom.length; i ++) {
+            if (nodesFrom[i].mType != nodesTo[i].mType
+                    || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Update the target's data to match the source.
+     * Before calling this, make sure canMorph(target, source) is true.
+     *
+     * @param target The target path represented in an array of PathDataNode
+     * @param source The source path represented in an array of PathDataNode
+     */
+    public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
+        for (int i = 0; i < source.length; i ++) {
+            target[i].mType = source[i].mType;
+            for (int j = 0; j < source[i].mParams.length; j ++) {
+                target[i].mParams[j] = source[i].mParams[j];
+            }
+        }
+    }
+
+    private static int nextStart(String s, int end) {
+        char c;
+
+        while (end < s.length()) {
+            c = s.charAt(end);
+            // Note that 'e' or 'E' are not valid path commands, but could be
+            // used for floating point numbers' scientific notation.
+            // Therefore, when searching for next command, we should ignore 'e'
+            // and 'E'.
+            if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+                    && c != 'e' && c != 'E') {
+                return end;
+            }
+            end++;
+        }
+        return end;
+    }
+
+    private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
+        list.add(new PathDataNode(cmd, val));
+    }
+
+    private static class ExtractFloatResult {
+        // We need to return the position of the next separator and whether the
+        // next float starts with a '-' or a '.'.
+        int mEndPosition;
+        boolean mEndWithNegOrDot;
+    }
+
+    /**
+     * Parse the floats in the string.
+     * This is an optimized version of parseFloat(s.split(",|\\s"));
+     *
+     * @param s the string containing a command and list of floats
+     * @return array of floats
+     */
+    private static float[] getFloats(String s) {
+        if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
+            return new float[0];
+        }
+        try {
+            float[] results = new float[s.length()];
+            int count = 0;
+            int startPosition = 1;
+            int endPosition = 0;
+
+            ExtractFloatResult result = new ExtractFloatResult();
+            int totalLength = s.length();
+
+            // The startPosition should always be the first character of the
+            // current number, and endPosition is the character after the current
+            // number.
+            while (startPosition < totalLength) {
+                extract(s, startPosition, result);
+                endPosition = result.mEndPosition;
+
+                if (startPosition < endPosition) {
+                    results[count++] = Float.parseFloat(
+                            s.substring(startPosition, endPosition));
+                }
+
+                if (result.mEndWithNegOrDot) {
+                    // Keep the '-' or '.' sign with next number.
+                    startPosition = endPosition;
+                } else {
+                    startPosition = endPosition + 1;
+                }
+            }
+            return copyOfRange(results, 0, count);
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("error in parsing \"" + s + "\"", e);
+        }
+    }
+
+    /**
+     * Calculate the position of the next comma or space or negative sign
+     * @param s the string to search
+     * @param start the position to start searching
+     * @param result the result of the extraction, including the position of the
+     * the starting position of next number, whether it is ending with a '-'.
+     */
+    private static void extract(String s, int start, ExtractFloatResult result) {
+        // Now looking for ' ', ',', '.' or '-' from the start.
+        int currentIndex = start;
+        boolean foundSeparator = false;
+        result.mEndWithNegOrDot = false;
+        boolean secondDot = false;
+        boolean isExponential = false;
+        for (; currentIndex < s.length(); currentIndex++) {
+            boolean isPrevExponential = isExponential;
+            isExponential = false;
+            char currentChar = s.charAt(currentIndex);
+            switch (currentChar) {
+                case ' ':
+                case ',':
+                    foundSeparator = true;
+                    break;
+                case '-':
+                    // The negative sign following a 'e' or 'E' is not a separator.
+                    if (currentIndex != start && !isPrevExponential) {
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case '.':
+                    if (!secondDot) {
+                        secondDot = true;
+                    } else {
+                        // This is the second dot, and it is considered as a separator.
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case 'e':
+                case 'E':
+                    isExponential = true;
+                    break;
+            }
+            if (foundSeparator) {
+                break;
+            }
+        }
+        // When there is nothing found, then we put the end position to the end
+        // of the string.
+        result.mEndPosition = currentIndex;
+    }
+
+    /**
+     * Each PathDataNode represents one command in the "d" attribute of the svg
+     * file.
+     * An array of PathDataNode can represent the whole "d" attribute.
+     */
+    public static class PathDataNode {
+        private char mType;
+        private float[] mParams;
+
+        private PathDataNode(char type, float[] params) {
+            mType = type;
+            mParams = params;
+        }
+
+        private PathDataNode(PathDataNode n) {
+            mType = n.mType;
+            mParams = copyOfRange(n.mParams, 0, n.mParams.length);
+        }
+
+        /**
+         * Convert an array of PathDataNode to Path.
+         *
+         * @param node The source array of PathDataNode.
+         * @param path The target Path object.
+         */
+        public static void nodesToPath(PathDataNode[] node, Path path) {
+            float[] current = new float[6];
+            char previousCommand = 'm';
+            for (int i = 0; i < node.length; i++) {
+                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+                previousCommand = node[i].mType;
+            }
+        }
+
+        /**
+         * The current PathDataNode will be interpolated between the
+         * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+         * <code>fraction</code>.
+         *
+         * @param nodeFrom The start value as a PathDataNode.
+         * @param nodeTo The end value as a PathDataNode
+         * @param fraction The fraction to interpolate.
+         */
+        public void interpolatePathDataNode(PathDataNode nodeFrom,
+                PathDataNode nodeTo, float fraction) {
+            for (int i = 0; i < nodeFrom.mParams.length; i++) {
+                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+                        + nodeTo.mParams[i] * fraction;
+            }
+        }
+
+        private static void addCommand(Path path, float[] current,
+                char previousCmd, char cmd, float[] val) {
+
+            int incr = 2;
+            float currentX = current[0];
+            float currentY = current[1];
+            float ctrlPointX = current[2];
+            float ctrlPointY = current[3];
+            float currentSegmentStartX = current[4];
+            float currentSegmentStartY = current[5];
+            float reflectiveCtrlPointX;
+            float reflectiveCtrlPointY;
+
+            switch (cmd) {
+                case 'z':
+                case 'Z':
+                    path.close();
+                    // Path is closed here, but we need to move the pen to the
+                    // closed position. So we cache the segment's starting position,
+                    // and restore it here.
+                    currentX = currentSegmentStartX;
+                    currentY = currentSegmentStartY;
+                    ctrlPointX = currentSegmentStartX;
+                    ctrlPointY = currentSegmentStartY;
+                    path.moveTo(currentX, currentY);
+                    break;
+                case 'm':
+                case 'M':
+                case 'l':
+                case 'L':
+                case 't':
+                case 'T':
+                    incr = 2;
+                    break;
+                case 'h':
+                case 'H':
+                case 'v':
+                case 'V':
+                    incr = 1;
+                    break;
+                case 'c':
+                case 'C':
+                    incr = 6;
+                    break;
+                case 's':
+                case 'S':
+                case 'q':
+                case 'Q':
+                    incr = 4;
+                    break;
+                case 'a':
+                case 'A':
+                    incr = 7;
+                    break;
+            }
+
+            for (int k = 0; k < val.length; k += incr) {
+                switch (cmd) {
+                    case 'm': // moveto - Start a new sub-path (relative)
+                        path.rMoveTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        currentSegmentStartX = currentX;
+                        currentSegmentStartY = currentY;
+                        break;
+                    case 'M': // moveto - Start a new sub-path
+                        path.moveTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        currentSegmentStartX = currentX;
+                        currentSegmentStartY = currentY;
+                        break;
+                    case 'l': // lineto - Draw a line from the current point (relative)
+                        path.rLineTo(val[k + 0], val[k + 1]);
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'L': // lineto - Draw a line from the current point
+                        path.lineTo(val[k + 0], val[k + 1]);
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
+                        path.rLineTo(val[k + 0], 0);
+                        currentX += val[k + 0];
+                        break;
+                    case 'H': // horizontal lineto - Draws a horizontal line
+                        path.lineTo(val[k + 0], currentY);
+                        currentX = val[k + 0];
+                        break;
+                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+                        path.rLineTo(0, val[k + 0]);
+                        currentY += val[k + 0];
+                        break;
+                    case 'V': // vertical lineto - Draws a vertical line from the current point
+                        path.lineTo(currentX, val[k + 0]);
+                        currentY = val[k + 0];
+                        break;
+                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
+                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+
+                        ctrlPointX = currentX + val[k + 2];
+                        ctrlPointY = currentY + val[k + 3];
+                        currentX += val[k + 4];
+                        currentY += val[k + 5];
+
+                        break;
+                    case 'C': // curveto - Draws a cubic Bézier curve
+                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+                        currentX = val[k + 4];
+                        currentY = val[k + 5];
+                        ctrlPointX = val[k + 2];
+                        ctrlPointY = val[k + 3];
+                        break;
+                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1],
+                                val[k + 2], val[k + 3]);
+
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'c' || previousCmd == 's'
+                                || previousCmd == 'C' || previousCmd == 'S') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 'q': // Draws a quadratic Bézier (relative)
+                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = currentX + val[k + 0];
+                        ctrlPointY = currentY + val[k + 1];
+                        currentX += val[k + 2];
+                        currentY += val[k + 3];
+                        break;
+                    case 'Q': // Draws a quadratic Bézier
+                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        ctrlPointX = val[k + 0];
+                        ctrlPointY = val[k + 1];
+                        currentX = val[k + 2];
+                        currentY = val[k + 3];
+                        break;
+                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+                        reflectiveCtrlPointX = 0;
+                        reflectiveCtrlPointY = 0;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = currentX - ctrlPointX;
+                            reflectiveCtrlPointY = currentY - ctrlPointY;
+                        }
+                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = currentX + reflectiveCtrlPointX;
+                        ctrlPointY = currentY + reflectiveCtrlPointY;
+                        currentX += val[k + 0];
+                        currentY += val[k + 1];
+                        break;
+                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
+                        reflectiveCtrlPointX = currentX;
+                        reflectiveCtrlPointY = currentY;
+                        if (previousCmd == 'q' || previousCmd == 't'
+                                || previousCmd == 'Q' || previousCmd == 'T') {
+                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+                        }
+                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                                val[k + 0], val[k + 1]);
+                        ctrlPointX = reflectiveCtrlPointX;
+                        ctrlPointY = reflectiveCtrlPointY;
+                        currentX = val[k + 0];
+                        currentY = val[k + 1];
+                        break;
+                    case 'a': // Draws an elliptical arc
+                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5] + currentX,
+                                val[k + 6] + currentY,
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX += val[k + 5];
+                        currentY += val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                    case 'A': // Draws an elliptical arc
+                        drawArc(path,
+                                currentX,
+                                currentY,
+                                val[k + 5],
+                                val[k + 6],
+                                val[k + 0],
+                                val[k + 1],
+                                val[k + 2],
+                                val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        currentX = val[k + 5];
+                        currentY = val[k + 6];
+                        ctrlPointX = currentX;
+                        ctrlPointY = currentY;
+                        break;
+                }
+                previousCmd = cmd;
+            }
+            current[0] = currentX;
+            current[1] = currentY;
+            current[2] = ctrlPointX;
+            current[3] = ctrlPointY;
+            current[4] = currentSegmentStartX;
+            current[5] = currentSegmentStartY;
+        }
+
+        private static void drawArc(Path p,
+                float x0,
+                float y0,
+                float x1,
+                float y1,
+                float a,
+                float b,
+                float theta,
+                boolean isMoreThanHalf,
+                boolean isPositiveArc) {
+
+            /* Convert rotation angle from degrees to radians */
+            double thetaD = Math.toRadians(theta);
+            /* Pre-compute rotation matrix entries */
+            double cosTheta = Math.cos(thetaD);
+            double sinTheta = Math.sin(thetaD);
+            /* Transform (x0, y0) and (x1, y1) into unit space */
+            /* using (inverse) rotation, followed by (inverse) scale */
+            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+            /* Compute differences and averages */
+            double dx = x0p - x1p;
+            double dy = y0p - y1p;
+            double xm = (x0p + x1p) / 2;
+            double ym = (y0p + y1p) / 2;
+            /* Solve for intersecting unit circles */
+            double dsq = dx * dx + dy * dy;
+            if (dsq == 0.0) {
+                Log.w(LOGTAG, " Points are coincident");
+                return; /* Points are coincident */
+            }
+            double disc = 1.0 / dsq - 1.0 / 4.0;
+            if (disc < 0.0) {
+                Log.w(LOGTAG, "Points are too far apart " + dsq);
+                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+                drawArc(p, x0, y0, x1, y1, a * adjust,
+                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
+                return; /* Points are too far apart */
+            }
+            double s = Math.sqrt(disc);
+            double sdx = s * dx;
+            double sdy = s * dy;
+            double cx;
+            double cy;
+            if (isMoreThanHalf == isPositiveArc) {
+                cx = xm - sdy;
+                cy = ym + sdx;
+            } else {
+                cx = xm + sdy;
+                cy = ym - sdx;
+            }
+
+            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+
+            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+
+            double sweep = (eta1 - eta0);
+            if (isPositiveArc != (sweep >= 0)) {
+                if (sweep > 0) {
+                    sweep -= 2 * Math.PI;
+                } else {
+                    sweep += 2 * Math.PI;
+                }
+            }
+
+            cx *= a;
+            cy *= b;
+            double tcx = cx;
+            cx = cx * cosTheta - cy * sinTheta;
+            cy = tcx * sinTheta + cy * cosTheta;
+
+            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+        }
+
+        /**
+         * Converts an arc to cubic Bezier segments and records them in p.
+         *
+         * @param p The target for the cubic Bezier segments
+         * @param cx The x coordinate center of the ellipse
+         * @param cy The y coordinate center of the ellipse
+         * @param a The radius of the ellipse in the horizontal direction
+         * @param b The radius of the ellipse in the vertical direction
+         * @param e1x E(eta1) x coordinate of the starting point of the arc
+         * @param e1y E(eta2) y coordinate of the starting point of the arc
+         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+         * @param start The start angle of the arc on the ellipse
+         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+         */
+        private static void arcToBezier(Path p,
+                double cx,
+                double cy,
+                double a,
+                double b,
+                double e1x,
+                double e1y,
+                double theta,
+                double start,
+                double sweep) {
+            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+            // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+            // Maximum of 45 degrees per cubic Bezier segment
+            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+
+            double eta1 = start;
+            double cosTheta = Math.cos(theta);
+            double sinTheta = Math.sin(theta);
+            double cosEta1 = Math.cos(eta1);
+            double sinEta1 = Math.sin(eta1);
+            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+            double anglePerSegment = sweep / numSegments;
+            for (int i = 0; i < numSegments; i++) {
+                double eta2 = eta1 + anglePerSegment;
+                double sinEta2 = Math.sin(eta2);
+                double cosEta2 = Math.cos(eta2);
+                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+                double alpha =
+                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+                double q1x = e1x + alpha * ep1x;
+                double q1y = e1y + alpha * ep1y;
+                double q2x = e2x - alpha * ep2x;
+                double q2y = e2y - alpha * ep2y;
+
+                p.cubicTo((float) q1x,
+                        (float) q1y,
+                        (float) q2x,
+                        (float) q2y,
+                        (float) e2x,
+                        (float) e2y);
+                eta1 = eta2;
+                e1x = e2x;
+                e1y = e2y;
+                ep1x = ep2x;
+                ep1y = ep2y;
+            }
+        }
+    }
+}
diff --git a/v7/vectordrawable/src/android/support/v7/graphics/drawable/VectorDrawableCompat.java b/v7/vectordrawable/src/android/support/v7/graphics/drawable/VectorDrawableCompat.java
new file mode 100644
index 0000000..f651999
--- /dev/null
+++ b/v7/vectordrawable/src/android/support/v7/graphics/drawable/VectorDrawableCompat.java
@@ -0,0 +1,169 @@
+/*
+ * 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.v7.graphics.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Support lib for {@link android.graphics.drawable.VectorDrawable}
+ * This is a duplication of VectorDrawable.java from frameworks/base, with major
+ * changes in XML parsing parts.
+ */
+public class VectorDrawableCompat extends Drawable {
+    private static final String LOG_TAG = "VectorDrawable";
+
+    private static final String SHAPE_CLIP_PATH = "clip-path";
+    private static final String SHAPE_GROUP = "group";
+    private static final String SHAPE_PATH = "path";
+    private static final String SHAPE_VECTOR = "vector";
+
+    private static final boolean DBG_VECTOR_DRAWABLE = false;
+
+    private Path mPath;
+
+    @Override
+    public void draw(Canvas canvas) {
+        // Now just draw the last path.
+        // TODO: Be able to draw the vector drawable's group tree into the canvas.
+        canvas.drawRGB(255, 0, 0);
+
+        Paint testPaint = new Paint();
+        testPaint.setColor(0xff101010);
+        canvas.drawPath(mPath, testPaint);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+
+    public static VectorDrawableCompat createFromResource(Resources res, int id) {
+        XmlPullParserFactory xppf = null;
+        XmlPullParser parser = null;
+        try {
+            xppf = XmlPullParserFactory.newInstance();
+            parser = xppf.newPullParser();
+            InputStream is = res.openRawResource(id);
+            parser.setInput(is, null);
+            // TODO: Use this getXml when the aapt is able to help us to keep the
+            // attributes for v-21 in the compiled version.
+            // XmlPullParser parser = res.getXml(id);
+
+            final AttributeSet attrs = Xml.asAttributeSet(parser);
+            final VectorDrawableCompat drawable = new VectorDrawableCompat();
+
+            drawable.inflateInternal(res, parser, attrs);
+
+            return drawable;
+        } catch (XmlPullParserException e) {
+            Log.e(LOG_TAG, "XmlPullParser exception for res id : " + id);
+        }
+        return null;
+    }
+
+    private Map<String, String> getAttributes(XmlPullParser parser) {
+        Map<String, String> attrs = null;
+        int attrCount = parser.getAttributeCount();
+        if (attrCount != -1) {
+            if (DBG_VECTOR_DRAWABLE) {
+                Log.v(LOG_TAG, "Attributes for [" + parser.getName() + "] " + attrCount);
+            }
+            attrs = new HashMap<String, String>(attrCount);
+            for (int i = 0; i < attrCount; i++) {
+                if (DBG_VECTOR_DRAWABLE) {
+                    Log.v(LOG_TAG, "\t[" + parser.getAttributeName(i) + "]=" +
+                            "[" + parser.getAttributeValue(i) + "]");
+                }
+                attrs.put(parser.getAttributeName(i), parser.getAttributeValue(i));
+            }
+        }
+        return attrs;
+    }
+
+    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs) {
+        // TODO: Add more details in the parsing to reconstruct the
+        // VectorDrawable data structure.
+        int eventType;
+        try {
+            eventType = parser.getEventType();
+
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                if (eventType == XmlPullParser.START_DOCUMENT) {
+                    if (DBG_VECTOR_DRAWABLE) {
+                        Log.v(LOG_TAG, "Start document");
+                    }
+                } else if (eventType == XmlPullParser.START_TAG) {
+                    if (DBG_VECTOR_DRAWABLE) {
+                        Log.v(LOG_TAG,"Parsing Attributes for ["+parser.getName()+"]");
+                    }
+                    Map<String,String> attributes = getAttributes(parser);
+                    if (attributes != null) {
+                        final String tagName = parser.getName();
+                        if (SHAPE_PATH.equals(tagName)) {
+                            String pathString = attributes.get("android:pathData");
+                            if (DBG_VECTOR_DRAWABLE) {
+                                Log.v(LOG_TAG, "pathData is " + pathString);
+                            }
+                            mPath = PathParser.createPathFromPathData(pathString);
+                        }
+                    }
+                } else if (eventType == XmlPullParser.END_TAG) {
+                    if (DBG_VECTOR_DRAWABLE) {
+                        Log.v(LOG_TAG, "End tag " + parser.getName());
+                    }
+                } else if (eventType == XmlPullParser.TEXT) {
+                    if (DBG_VECTOR_DRAWABLE) {
+                        Log.v(LOG_TAG, "Text " + parser.getText());
+                    }
+                }
+                eventType = parser.next();
+            }
+        } catch (XmlPullParserException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        if (DBG_VECTOR_DRAWABLE) {
+            Log.v(LOG_TAG, "End document");
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/v7/vectordrawable/tests/Android.mk b/v7/vectordrawable/tests/Android.mk
new file mode 100644
index 0000000..1fef869
--- /dev/null
+++ b/v7/vectordrawable/tests/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR = \
+        $(LOCAL_PATH)/res \
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-vectordrawable
+
+LOCAL_PACKAGE_NAME := AndroidVectorDrawableTests
+
+include $(BUILD_PACKAGE)
diff --git a/v7/vectordrawable/tests/AndroidManifest.xml b/v7/vectordrawable/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b0cdc53
--- /dev/null
+++ b/v7/vectordrawable/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.support.v7.vectordrawable" >
+
+    <uses-sdk android:minSdkVersion="7" />
+
+    <application android:icon="@drawable/app_sample_code" android:label="VectorSupportTest" >
+        <activity android:name="android.support.v7.vectordrawable.TestActivity" />
+
+        <intent-filter>
+            <action android:name="android.intent.action.MAIN" />
+
+            <category android:name="android.intent.category.LAUNCHER" />
+        </intent-filter>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/v7/vectordrawable/tests/res/drawable/app_sample_code.png b/v7/vectordrawable/tests/res/drawable/app_sample_code.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/v7/vectordrawable/tests/res/drawable/app_sample_code.png
Binary files differ
diff --git a/v7/vectordrawable/tests/res/drawable/vector_drawable01.xml b/v7/vectordrawable/tests/res/drawable/vector_drawable01.xml
new file mode 100644
index 0000000..6c376db
--- /dev/null
+++ b/v7/vectordrawable/tests/res/drawable/vector_drawable01.xml
@@ -0,0 +1,31 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="48dp"
+        android:width="48dp"
+        android:viewportHeight="480"
+        android:viewportWidth="480" >
+
+    <group>
+        <path
+            android:name="box1"
+            android:pathData="m20,200l100,90l180-180l-35-35l-145,145l-60-60l-40,40z"
+            android:fillColor="?android:attr/colorControlActivated"
+            android:strokeColor="?android:attr/colorControlActivated"
+            android:strokeLineCap="round"
+            android:strokeLineJoin="round" />
+    </group>
+</vector>
diff --git a/v7/vectordrawable/tests/res/raw/vector_drawable01.xml b/v7/vectordrawable/tests/res/raw/vector_drawable01.xml
new file mode 100644
index 0000000..705cc34
--- /dev/null
+++ b/v7/vectordrawable/tests/res/raw/vector_drawable01.xml
@@ -0,0 +1,31 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="48dp"
+        android:width="48dp"
+        android:viewportHeight="480"
+        android:viewportWidth="480" >
+
+    <group>
+        <path
+            android:name="box1"
+            android:pathData="m20,200l100,90l180-180l-35-35l-145,145l-60-60l-40,40z"
+            android:fillColor="?android:attr/colorControlActivated"
+            android:strokeColor="?android:attr/colorControlActivated"
+            android:strokeLineCap="round"
+            android:strokeLineJoin="round" />
+    </group>
+</vector>
diff --git a/v7/vectordrawable/tests/src/android/support/v7/vectordrawable/TestActivity.java b/v7/vectordrawable/tests/src/android/support/v7/vectordrawable/TestActivity.java
new file mode 100644
index 0000000..a547086
--- /dev/null
+++ b/v7/vectordrawable/tests/src/android/support/v7/vectordrawable/TestActivity.java
@@ -0,0 +1,97 @@
+/*
+ * 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.v7.vectordrawable;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.support.v7.graphics.drawable.VectorDrawableCompat;
+import android.view.View;
+
+import java.io.InputStream;
+
+/**
+ * @hide from javadoc
+ */
+public class TestActivity extends Activity {
+    private static final String LOG_TAG = "TestActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new SampleView(this));
+    }
+
+    private static class SampleView extends View {
+        private Bitmap mBitmap;
+        private VectorDrawableCompat mDrawable;
+
+        private static void drawIntoBitmap(Bitmap bm) {
+            float x = bm.getWidth();
+            float y = bm.getHeight();
+            Canvas c = new Canvas(bm);
+            Paint p = new Paint();
+            p.setAntiAlias(true);
+
+            p.setAlpha(0x80);
+            c.drawCircle(x/2, y/2, x/2, p);
+
+            p.setAlpha(0x30);
+            p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+            p.setTextSize(60);
+            p.setTextAlign(Paint.Align.CENTER);
+            Paint.FontMetrics fm = p.getFontMetrics();
+            c.drawText("Alpha", x/2, (y-fm.ascent)/2, p);
+        }
+
+        public SampleView(Context context) {
+            super(context);
+            setFocusable(true);
+
+            InputStream is = context.getResources().openRawResource(R.drawable.app_sample_code);
+            mBitmap = BitmapFactory.decodeStream(is);
+
+            // TODO: use R.drawable.vector_drawable01 when the aapt is able to
+            // support.
+            mDrawable = VectorDrawableCompat.createFromResource(context.getResources(), R.raw.vector_drawable01);
+        }
+
+        @Override protected void onDraw(Canvas canvas) {
+            canvas.drawColor(Color.WHITE);
+
+            mDrawable.draw(canvas);
+
+            Paint p = new Paint();
+            float y = 10;
+
+            p.setColor(Color.RED);
+            canvas.drawBitmap(mBitmap, 10, y, p);
+            y += mBitmap.getHeight() + 10;
+
+        }
+    }
+
+}