Merge tag android-5.1.0_r1 into AOSP_5.1_MERGE

Change-Id: Iba7bb4a34fc51030e432e9ebef756aa4c01be76e
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/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/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/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 f74085c..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..395dfd1 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -16,7 +16,7 @@
 -->
 
 <!-- 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:layout_width="match_parent"
@@ -24,8 +24,8 @@
     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>
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..1a60828 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 43f02bc..9d38d3c 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -22,44 +22,28 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <skip />
+    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Poista Tykkään-valinta"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Valitse Tykkään"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Poista En tykkää -valinta"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Valitse En tykkää"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ei uudelleentoistoa"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Toista kaikki uudelleen"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Toista yksi uudelleen"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Ota satunnaistoisto käyttöön"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Poista satunnaistoisto käytöstä"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Ota korkea laatu käyttöön"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Poista korkea laatu käytöstä"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ota tekstitys käyttöön"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Poista tekstitys käytöstä"</string>
 </resources>
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..a3884d8 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -22,10 +22,14 @@
     <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>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 2d40dad..a926396 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -22,22 +22,26 @@
     <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>
-    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"पसंदीदा का चयन न करें"</string>
+    <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"अधिक विकल्प"</string>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"पसंदीदा को ना चुनें"</string>
     <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"पसंदीदा चुनें"</string>
-    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"नापसंद का चयन न करें"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"नापसंद को ना चुनें"</string>
     <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"नापसंद चुनें"</string>
     <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"कुछ भी न दोहराएं"</string>
     <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"सभी को दोहराएं"</string>
     <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"एक दोहराएं"</string>
-    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"शफ़ल सक्षम करें"</string>
-    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"शफ़ल अक्षम करें"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"फेर-बदल सक्षम करें"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"फेर-बदल अक्षम करें"</string>
     <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"उच्च गुणवत्ता सक्षम करें"</string>
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करें"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षक सक्षम करें"</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 5a26a28..427f1cd 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -22,12 +22,16 @@
     <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_skip_next" msgid="2946499493161095772">"A következő átugrása"</string>
-    <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Az előző átugrása"</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>
     <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"„Tetszik” értékelés visszavonása"</string>
     <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"„Tetszik” értékelés kiválasztása"</string>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 3d1ad21..7e8112e 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -22,44 +22,28 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <skip />
+    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Ապանշել Հավանելու կոճակը"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Նշել Հավանելու կոճակը"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Ապանշել Չհավանելու կոճակը"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Նշել Չհավանելու կոճակը"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Չկրկնել"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Կրկնել բոլորը"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Կրկնել մեկը"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Միացնել խառը նվագարկումը"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Անջատել խառը նվագարկումը"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Միացնել բարձր որակը"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Անջատել բարձր որակը"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Միացնել խորագրերը"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Անջատել խորագրերը"</string>
 </resources>
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 03933ac..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 273bd88..70aeada 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -22,44 +22,32 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
+    <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 />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
+    <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 />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"მაღალი შეფასების არჩევის გაუქმება"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"მაღალი შეფასების არჩევა"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"დაბალი შეფასების არჩევის გაუქმება"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"დაბალი შეფასების არჩევა"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"არცერთის გამეორება"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"ყველას გამეორება"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"ერთის გამეორება"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"არეულად დაკვრის ჩართვა"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"არეულად დაკვრის გამორთვა"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"მაღალი ხარისხის ჩართვა"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"მაღალი ხარისხის გამორთვა"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"დახურული წარწერების ჩართვა"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"დახურული წარწერების გაუქმება"</string>
 </resources>
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 6c10e6c..ea7d0f4 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 acda291..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>
@@ -40,6 +44,6 @@
     <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"​ປິດ​ນຳ​ໃຊ້​ການ​ສະຫຼັບ"</string>
     <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"​ເປີດນຳ​ໃຊ້​ການຫຼິ້ນ​ດ້ວຍຄຸນ​ນະ​ພາບ​ສູງ"</string>
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"​ປິດ​ນຳ​ໃຊ້​ການຫຼິ້ນ​ດ້ວຍຄຸນ​ນະ​ພາບ​ສູງ"</string>
-    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"​ເປີດ​ນຳ​ໃຊ້​ຄຳ​ອະ​ທິ​ບາຍ​ລະ​ອຽດ"</string>
-    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"​ປິດ​ນຳ​ໃຊ້​ຄຳ​ອະ​ທິ​ບາຍ​ລະ​ອຽດ"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"​ເປີດ​ນຳ​ໃຊ້​​ຄຳ​ບັນ​ຍາຍ​ແບບ​ປິດ"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"​ປິດ​ນຳ​ໃຊ້​ຄຳ​ບັນ​ຍາຍ​ແບບ​ປິດ"</string>
 </resources>
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 9893fda..c073e43 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -22,44 +22,28 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <skip />
+    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Nyahpilih Bagus"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Pilih Bagus"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Nyahpilih Tidak Bagus"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Pilih Tidak Bagus"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Jangan Ulang"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ulang Semua"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ulang Satu"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Dayakan Rombak"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Lumpuhkan Rombak"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Dayakan Kualiti Tinggi"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Lumpuhkan Kualiti Tinggi"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Dayakan Kapsyen Tertutup"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Lumpuhkan Kapsyen Tertutup"</string>
 </resources>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index 884e026..77a2271 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 7d6efa5..cb6aa4a 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -22,44 +22,28 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <skip />
+    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Deselectează „Îmi place”"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Selectează „Îmi place”"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Deselectează „Nu-mi place”"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Selectează „Nu-mi place”"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Nu repetă"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Repetă toate"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Repetă unul"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Activează redarea în mod aleatoriu"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Dezactivează redarea în mod aleatoriu"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Activează calitatea înaltă"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Dezactivează calitatea înaltă"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activează subtitrările"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Dezactivează subtitrările"</string>
 </resources>
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 3566f0e..e5c0cf4 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -22,44 +22,28 @@
     <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>
-    <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
-    <skip />
+    <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>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"මහපටැඟිල්ල ඉහළට තිබීම තේරීම නොකරන්න"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"මහපටැඟිල්ල ඉහළට තිබීම තේරීම කරන්න"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"මහපටැඟිල්ල පහළට තිබීම තේරීම නොකරන්න"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"මහපටැඟිල්ල පහළට තිබීම තේරීම කරන්න"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"නැවත කරන්න කිසිවක් නැත"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"සියල්ල නැවත කරන්න"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"එකක් නැවත කරන්න"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"ඇනීම සබල කරන්න"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"ඇනීම අබල කරන්න"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"උපරිම ගුණත්වය සබල කරන ලදි"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"උපරිම ගුණත්වය අබල කරන ලදි"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"වැසුණු ශිර්ෂ කිරීම සබල කරන ලදි"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"වැසුණු ශිර්ෂ කිරීම අබල කරන ලදි"</string>
 </resources>
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 5ba438d..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 b90a544..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>
@@ -40,6 +44,6 @@
     <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"I-disable ang Shuffle"</string>
     <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"I-enable ang Mataas na Kalidad"</string>
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"I-disable ang Mataas na Kalidad"</string>
-    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Nakasarang Caption"</string>
-    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Nakasarang Caption"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Subtitle"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Subtitle"</string>
 </resources>
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 d13641d..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>
@@ -40,6 +44,6 @@
     <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"شفل کو غیر فعال کریں"</string>
     <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"اعلی معیار کو فعال کریں"</string>
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"اعلی معیار کو غیر فعال کریں"</string>
-    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ذیلی سرخیاں لگانے کو فعال کریں"</string>
-    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ذیلی سرخیاں لگانے کو غیر فعال کریں"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"سب ٹائٹلز کو فعال کریں"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"سب ٹائٹلز کو غیر فعال کریں"</string>
 </resources>
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 3c57156..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_rewind" msgid="2227196334132350684">"回放"</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>
@@ -35,11 +39,11 @@
     <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"选择踩操作"</string>
     <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重复播放"</string>
     <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重复播放全部"</string>
-    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重复播放单个视频"</string>
-    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"启用随机播放"</string>
-    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"停用随机播放"</string>
-    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"启用高画质模式"</string>
-    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高画质模式"</string>
-    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"启用特制字幕"</string>
-    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭特制字幕"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重复播放一项"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"开启随机播放"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"关闭随机播放"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"开启高画质模式"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"关闭高画质模式"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"开启字幕"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭字幕"</string>
 </resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 88776c5..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>
@@ -33,7 +37,7 @@
     <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"選取喜歡"</string>
     <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"取消選取不喜歡"</string>
     <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"選取不喜歡"</string>
-    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放任何媒體項目"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放"</string>
     <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有媒體項目"</string>
     <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放一個媒體項目"</string>
     <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"啟用隨機播放"</string>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index c1378a2..67efc40 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -22,20 +22,24 @@
     <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_skip_next" msgid="2946499493161095772">"跳至下一個媒體項目"</string>
-    <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"跳至上一個媒體項目"</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>
     <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"取消選取喜歡"</string>
     <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"選取喜歡"</string>
     <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"取消選取不喜歡"</string>
     <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"選取不喜歡"</string>
     <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"不重複播放"</string>
-    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有媒體項目"</string>
-    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放單一媒體項目"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"重複播放所有項目"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"重複播放單一項目"</string>
     <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"啟用隨機播放"</string>
     <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"停用隨機播放"</string>
     <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"啟用高品質播放"</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..3fef5ee 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>
@@ -65,25 +64,28 @@
 
     <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 +102,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 +113,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 +133,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 +147,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 +176,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..03be3ac 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>
@@ -150,8 +155,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 +169,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 +211,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 +239,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 +248,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..7346753 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -40,6 +40,7 @@
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
+import android.support.v4.app.FragmentActivity;
 import android.support.v4.content.ContextCompat;
 
 /**
@@ -337,6 +338,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,10 +351,24 @@
             // 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;
@@ -365,7 +383,11 @@
         }
         ta.recycle();
 
-        createFragment(activity);
+        if (isSupportFragmentActivity) {
+            createSupportFragment((FragmentActivity) activity);
+        } else {
+            createFragment(activity);
+        }
     }
 
     private void createFragment(Activity activity) {
@@ -384,6 +406,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.
      */
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..2207ac6 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";
@@ -185,7 +190,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;
@@ -200,17 +205,14 @@
     private PresenterSelector mHeaderPresenterSelector;
 
     // 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 +523,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 +567,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 +617,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 +724,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 +745,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 +789,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);
     }
 
@@ -880,22 +878,39 @@
 
     private class SetSelectionRunnable implements Runnable {
         int mPosition;
+        boolean mSmooth = true;
         @Override
         public void run() {
-            setSelection(mPosition);
+            setSelection(mPosition, mSmooth);
         }
     }
 
     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.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+    }
+
     @Override
     public void onStart() {
         super.onStart();
@@ -904,8 +919,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 +930,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 +1065,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..68b7af3
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -0,0 +1,1115 @@
+/* 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 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;
+
+    // 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.mPosition = position;
+            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+
+            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 class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        boolean mSmooth = true;
+        @Override
+        public void run() {
+            setSelection(mPosition, mSmooth);
+        }
+    }
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    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.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+    }
+
+    @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..d8ae895
--- /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..0d81005 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);
         }
     };
 
@@ -210,16 +212,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 +247,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 +278,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 +326,6 @@
             fade(true);
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
-        getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
     }
 
@@ -381,6 +428,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..a453f24
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -0,0 +1,741 @@
+/* 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());
+        }
+    }
+
+    /**
+     * 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..a5e5f0a 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -15,6 +15,7 @@
 
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.Presenter;
@@ -26,7 +27,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;
@@ -300,8 +303,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 +351,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;
     }
@@ -369,6 +374,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..4353a5d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -0,0 +1,414 @@
+/* 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.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(mRowSelectedListener);
+        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 mRowSelectedListener =
+            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 (mOnItemSelectedListener != null) {
+                mOnItemSelectedListener.onItemSelected(item, row);
+            }
+            if (mOnItemViewSelectedListener != null) {
+                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    /**
+     * 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 onRowSelected(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;
+        }
+    }
+
+    /**
+     * 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);
+
+        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/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/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..d11e5b1 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -88,6 +88,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 +100,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 +130,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 +151,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,6 +167,9 @@
      */
     public void clear() {
         int itemCount = mItems.size();
+        if (itemCount == 0) {
+            return;
+        }
         mItems.clear();
         notifyItemRangeRemoved(0, itemCount);
     }
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..bb81bb6 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.
@@ -179,6 +187,10 @@
         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);
     }
 
     protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
@@ -248,11 +260,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 +274,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 +293,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);
@@ -476,7 +493,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}
      */
@@ -669,16 +686,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 +694,14 @@
     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);
+    }
+
+
 }
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/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
index 7bf2faf..8556e16 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -467,7 +467,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);
@@ -530,4 +530,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 6052c81..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/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index b6c4fa0..e32e30f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -61,7 +61,7 @@
         // For placement
         private int mLeftInset;
         private int mTopInset;
-        private int mRighInset;
+        private int mRightInset;
         private int mBottomInset;
 
         // For alignment
@@ -109,7 +109,7 @@
         }
 
         int getOpticalRight(View view) {
-            return view.getRight() - mRighInset;
+            return view.getRight() - mRightInset;
         }
 
         int getOpticalBottom(View view) {
@@ -117,7 +117,7 @@
         }
 
         int getOpticalWidth(View view) {
-            return view.getWidth() - mLeftInset - mRighInset;
+            return view.getWidth() - mLeftInset - mRightInset;
         }
 
         int getOpticalHeight(View view) {
@@ -129,7 +129,7 @@
         }
 
         int getOpticalRightInset() {
-            return mRighInset;
+            return mRightInset;
         }
 
         int getOpticalTopInset() {
@@ -151,7 +151,7 @@
         void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
             mLeftInset = leftInset;
             mTopInset = topInset;
-            mRighInset = rightInset;
+            mRightInset = rightInset;
             mBottomInset = bottomInset;
         }
 
@@ -174,6 +174,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;
@@ -269,7 +293,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.
      */
@@ -343,14 +367,19 @@
      */
     private boolean mScrollEnabled = true;
 
-    /**
-     * Percent of overreach.
-     */
-    private float mPrimaryOverReach = 1f;
-
     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.
      */
     private int[] mMeasuredDimension = new int[2];
@@ -373,6 +402,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,14 +544,6 @@
         mChildSelectedListener = listener;
     }
 
-    public void setPrimaryOverReach(float fraction) {
-        if (fraction != mPrimaryOverReach) {
-            if (DEBUG) Log.v(getTag(), "setPrimaryOverReach " + fraction);
-            mPrimaryOverReach = fraction;
-            requestLayout();
-        }
-    }
-
     private int getPositionByView(View view) {
         if (view == null) {
             return NO_POSITION;
@@ -532,16 +564,34 @@
         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;
+
+        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);
+        }
+
+        // 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
@@ -659,10 +709,14 @@
 
         final int newItemCount = mState.getItemCount();
 
-        if (focusPosition == NO_POSITION && newItemCount > 0) {
+        // Force the re-init path in the following conditional
+        if (newItemCount == 0) {
+            focusPosition = NO_POSITION;
+        } else 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.
@@ -741,6 +795,7 @@
 
             // Same adapter, we can reuse any attached views
             detachAndScrapAttachedViews(mRecycler);
+            updateScrollController();
 
         } else {
             // otherwise recreate data structure
@@ -750,6 +805,7 @@
                 mRows[i] = new StaggeredGrid.Row();
             }
             mGrid = new StaggeredGridDefault();
+            mGrid.setReversedFlow(mOrientation == HORIZONTAL && mReverseFlowPrimary);
             if (newItemCount == 0) {
                 focusPosition = NO_POSITION;
             } else if (focusPosition >= newItemCount) {
@@ -759,9 +815,7 @@
             // Adapter may have changed so remove all attached views permanently
             removeAndRecycleAllViews(mRecycler);
 
-            mScrollOffsetPrimary = 0;
-            mScrollOffsetSecondary = 0;
-            mWindowAlignment.reset();
+            initScrollController();
         }
 
         mGrid.setProvider(mGridProvider);
@@ -769,7 +823,6 @@
         mGrid.setRows(mRows);
         mFirstVisiblePos = mLastVisiblePos = NO_POSITION;
 
-        initScrollController();
         updateScrollSecondAxis();
 
         return focusPosition;
@@ -787,14 +840,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,
@@ -845,9 +907,18 @@
                 }
             }
 
-            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);
@@ -1091,24 +1162,23 @@
             int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
             int start, end;
             final boolean rowIsEmpty = mRows[rowIndex].high == mRows[rowIndex].low;
-            if (append) {
+            boolean addHigh = (!mReverseFlowPrimary) ? append : !append;
+            int lowVisiblePos = (!mReverseFlowPrimary) ? mFirstVisiblePos : mLastVisiblePos;
+            int highVisiblePos = (!mReverseFlowPrimary) ? mLastVisiblePos : mFirstVisiblePos;
+            if (addHigh) {
                 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;
+                    if (lowVisiblePos >= 0) {
+                        int rowOfLowPos = mGrid.getLocation(lowVisiblePos).row;
+                        // If row is after row of lowest position,
+                        // start a new column after the first row.
+                        if (rowIndex < rowOfLowPos) {
+                            mRows[rowIndex].low = mRows[rowOfLowPos].high + mMarginPrimary;
                         }
-                    } else {
-                        start = 0;
                     }
-                    mRows[rowIndex].low = start;
+                    start = mRows[rowIndex].low;
                 }
                 end = start + length;
                 mRows[rowIndex].high = end;
@@ -1116,25 +1186,17 @@
                 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;
+                    if (highVisiblePos >= 0) {
+                        int rowOfHighPos = mGrid.getLocation(highVisiblePos).row;
+                        if (mOrientation == HORIZONTAL ?
+                                rowIndex < rowOfHighPos : rowIndex > rowOfHighPos) {
+                            mRows[rowIndex].high = mRows[rowOfHighPos].low - mMarginPrimary;
                         }
-                    } else {
-                        start = 0;
-                        end = length;
                     }
-                    mRows[rowIndex].high = end;
+                    end = mRows[rowIndex].high;
                 }
+                start = end - length;
                 mRows[rowIndex].low = start;
             }
             if (mFirstVisiblePos < 0) {
@@ -1155,10 +1217,18 @@
                 Log.d(getTag(), "addView " + index + " " + v);
             }
             if (index == mFirstVisiblePos) {
-                updateScrollMin();
+                if (!mReverseFlowPrimary) {
+                    updateScrollMin();
+                } else {
+                    updateScrollMax();
+                }
             }
             if (index == mLastVisiblePos) {
-                updateScrollMax();
+                if (!mReverseFlowPrimary) {
+                    updateScrollMax();
+                } else {
+                    updateScrollMin();
+                }
             }
         }
     };
@@ -1170,7 +1240,9 @@
             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
@@ -1217,33 +1289,52 @@
     }
 
     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) {
+        if (mReverseFlowPrimary) {
+            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;
                 }
-            } else if (mRows[i].high < right - mMarginPrimary) {
-                return true;
+            }
+        } else {
+            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) {
+        if (mReverseFlowPrimary) {
+            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;
                 }
-            } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
-                return true;
+            }
+        } else {
+            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;
@@ -1264,7 +1355,11 @@
             } else if ((mLastVisiblePos == NO_POSITION && mState.getItemCount() > 0) ||
                     (mLastVisiblePos != NO_POSITION &&
                             mLastVisiblePos < mState.getItemCount() - 1)) {
-                mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+                if (mReverseFlowPrimary) {
+                    mGrid.appendItems(mScrollOffsetPrimary);
+                } else {
+                    mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+                }
                 return false;
             } else {
                 return true;
@@ -1293,7 +1388,11 @@
                         return false;
                     }
                 } else {
-                    mGrid.prependItems(mScrollOffsetPrimary);
+                    if (mReverseFlowPrimary) {
+                        mGrid.prependItems(mScrollOffsetPrimary + mSizePrimary);
+                    } else {
+                        mGrid.prependItems(mScrollOffsetPrimary);
+                    }
                     return false;
                 }
             } else {
@@ -1328,7 +1427,9 @@
         boolean update = false;
         while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
             View view = findViewByPosition(mLastVisiblePos);
-            if (getViewMin(view) > mSizePrimary) {
+            boolean offEnd = (!mReverseFlowPrimary) ? getViewMin(view) > mSizePrimary :
+                getViewMax(view) < 0;
+            if (offEnd) {
                 removeChildAt(mLastVisiblePos);
                 mLastVisiblePos--;
                 update = true;
@@ -1348,7 +1449,9 @@
         boolean update = false;
         while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
             View view = findViewByPosition(mFirstVisiblePos);
-            if (getViewMax(view) < 0) {
+            boolean offFront = (!mReverseFlowPrimary) ? getViewMax(view) < 0:
+                getViewMin(view) > mSizePrimary;
+            if (offFront) {
                 removeChildAt(mFirstVisiblePos);
                 mFirstVisiblePos++;
                 update = true;
@@ -1385,7 +1488,7 @@
 
     // Fast layout when there is no structure change, adapter change, etc.
     protected void fastRelayout(boolean scrollToFocus) {
-        initScrollController();
+        updateScrollController();
 
         List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
 
@@ -1493,12 +1596,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) {
@@ -1524,6 +1629,9 @@
                 if (DEBUG) Log.v(getTag(), "savedFocusPos " + savedFocusPos +
                         " mFocusPosition " + mFocusPosition);
             }
+            if (mFocusPosition == NO_POSITION) {
+                mBaseGridView.clearFocus();
+            }
 
             mWindowAlignment.mainAxis().invalidateScrollMin();
             mWindowAlignment.mainAxis().invalidateScrollMax();
@@ -1601,6 +1709,7 @@
         if (fastRelayout && mFocusPosition != savedFocusPos) {
             dispatchChildSelected();
         }
+
         mInLayout = false;
         leaveContext();
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
@@ -1668,16 +1777,20 @@
 
     // scroll in main direction may add/prune views
     private int scrollDirectionPrimary(int da) {
+        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;
                 }
@@ -1696,17 +1809,33 @@
         boolean updated;
 
         if (da > 0) {
-            appendVisibleItems();
+            if (mReverseFlowPrimary) {
+                prependVisibleItems();
+            } else {
+                appendVisibleItems();
+            }
         } else if (da < 0) {
-            prependVisibleItems();
+            if (mReverseFlowPrimary) {
+                appendVisibleItems();
+            } else {
+                prependVisibleItems();
+            }
         }
         updated = getChildCount() > childCount;
         childCount = getChildCount();
 
         if (da > 0) {
-            removeInvisibleViewsAtFront();
+            if (mReverseFlowPrimary) {
+                removeInvisibleViewsAtEnd();
+            } else {
+                removeInvisibleViewsAtFront();
+            }
         } else if (da < 0) {
-            removeInvisibleViewsAtEnd();
+            if (mReverseFlowPrimary) {
+                removeInvisibleViewsAtFront();
+            } else {
+                removeInvisibleViewsAtEnd();
+            }
         }
         updated |= getChildCount() < childCount;
 
@@ -1730,12 +1859,14 @@
     }
 
     private void updateScrollMax() {
-        if (mLastVisiblePos < 0) {
+        int highVisiblePos = (!mReverseFlowPrimary) ? mLastVisiblePos : mFirstVisiblePos;
+        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;
@@ -1747,17 +1878,18 @@
             }
         }
         int maxScroll = Integer.MAX_VALUE;
-        for (int i = mLastVisiblePos; i >= mFirstVisiblePos; i--) {
-            StaggeredGrid.Location location = mGrid.getLocation(i);
+        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+            int pos = mReverseFlowPrimary ? i : mLastVisiblePos-i+mFirstVisiblePos;
+            StaggeredGrid.Location location = mGrid.getLocation(pos);
             if (location != null && location.row == rowIndex) {
                 int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
                 mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
-                maxScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+                maxScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
                 mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
                 break;
             }
         }
-        if (lastAvailable) {
+        if (highAvailable) {
             mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
             mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
             if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
@@ -1774,12 +1906,14 @@
     }
 
     private void updateScrollMin() {
-        if (mFirstVisiblePos < 0) {
+        int lowVisiblePos = (!mReverseFlowPrimary) ? mFirstVisiblePos : mLastVisiblePos;
+        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;
@@ -1792,16 +1926,17 @@
         }
         int minScroll = Integer.MIN_VALUE;
         for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
-            StaggeredGrid.Location location = mGrid.getLocation(i);
+            int pos = mReverseFlowPrimary ? mLastVisiblePos-i+mFirstVisiblePos : i;
+            StaggeredGrid.Location location = mGrid.getLocation(pos);
             if (location != null && location.row == rowIndex) {
                 int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
                 mWindowAlignment.mainAxis().setMinEdge(minEdge);
-                minScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+                minScroll = getPrimarySystemScrollPosition(findViewByPosition(pos));
                 mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
                 break;
             }
         }
-        if (firstAvailable) {
+        if (lowAvailable) {
             mWindowAlignment.mainAxis().setMinEdge(minEdge);
             mWindowAlignment.mainAxis().setMinScroll(minScroll);
             if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
@@ -1823,6 +1958,23 @@
     }
 
     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),
@@ -1839,20 +1991,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 +2017,12 @@
     }
 
     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) {
         View view = findViewByPosition(position);
         if (view != null) {
             mInSelection = true;
@@ -1897,7 +2048,10 @@
                             return null;
                         }
                         final int firstChildPos = getPosition(getChildAt(0));
-                        final int direction = targetPosition < firstChildPos ? -1 : 1;
+                        // 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 {
@@ -1958,7 +2112,7 @@
 
     @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) {
@@ -1978,7 +2132,7 @@
     @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) {
@@ -1999,6 +2153,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 +2165,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,28 +2202,33 @@
     }
 
     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 = mFirstVisiblePos == 0;
+            isMax = mLastVisiblePos == (mState == null ?
+                    getItemCount() : mState.getItemCount()) - 1;
+        } else {
+            isMax = mFirstVisiblePos == 0;
+            isMin = mLastVisiblePos == (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) {
@@ -2070,10 +2236,15 @@
         int pos = getPositionByView(view);
         StaggeredGrid.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);
     }
 
     /**
@@ -2199,7 +2370,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 +2421,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);
             }
         }
     }
@@ -2404,26 +2573,27 @@
 
         View view = null;
         int movement = getMovement(direction);
+        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
         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()) {
+                if (newPos < getItemCount() && mScrollEnabled) {
                     setSelectionSmooth(mBaseGridView, newPos);
                     view = focused;
                 } else {
-                    if (!mFocusOutEnd) {
+                    if (isScroll || !mFocusOutEnd) {
                         view = focused;
                     }
                 }
             } else if (movement == PREV_ITEM){
                 int newPos = mFocusPosition - mNumRows;
-                if (newPos >= 0) {
+                if (newPos >= 0 && mScrollEnabled) {
                     setSelectionSmooth(mBaseGridView, newPos);
                     view = focused;
                 } else {
-                    if (!mFocusOutFront) {
+                    if (isScroll || !mFocusOutFront) {
                         view = focused;
                     }
                 }
@@ -2445,9 +2615,9 @@
             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;
+                    view = mFocusOutFront && !isScroll ? null : focused;
                 } else if (movement == NEXT_ITEM){
-                    view = mFocusOutEnd ? null : focused;
+                    view = mFocusOutEnd && !isScroll ? null : focused;
                 }
             }
             leaveContext();
@@ -2525,10 +2695,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 +2710,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;
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/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index a8deee4..fc0237b 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -17,11 +17,14 @@
 import android.content.res.TypedArray;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
+import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
 import android.util.Log;
 import android.view.View;
 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,6 +50,8 @@
     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;
@@ -87,6 +92,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;
@@ -184,10 +190,6 @@
         }
         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);
@@ -242,7 +244,8 @@
 
             @Override
             public void onAddPresenter(Presenter presenter, int type) {
-                rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(type, 24);
+                rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
+                        type, getRecycledPoolSize(presenter));
             }
         });
     }
@@ -252,6 +255,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) {
@@ -537,4 +555,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/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
index 03648c6..ddac678 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) {
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/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/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index 1c7ed3d..aa774db 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -200,6 +200,11 @@
      */
     protected void initializeRowViewHolder(ViewHolder vh) {
         vh.mInitialzed = true;
+        // set clip children to false for slide transition
+        ((ViewGroup) vh.view).setClipChildren(false);
+        if (vh.mContainerViewHolder != null) {
+            ((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
+        }
     }
 
     /**
@@ -490,8 +495,21 @@
 
     /**
      * 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.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..4b5abe8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -97,6 +97,7 @@
     protected Row[] mRows;
     protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
     private ArrayList<Integer>[] mTmpItemPositionsInRows;
+    protected boolean mReversedFlow;
 
     /**
      * A constant representing a default starting index, indicating that the
@@ -111,6 +112,10 @@
 
     protected int mFirstIndex = -1;
 
+    public void setReversedFlow(boolean reversedFlow) {
+        mReversedFlow = reversedFlow;
+    }
+
     /**
      * Sets the {@link Provider} for this staggered grid.
      *
@@ -246,7 +251,7 @@
     /**
      * Append items until the high edge reaches upTo.
      */
-    public abstract void appendItems(int upTo);
+    public abstract void appendItems(int toLimit);
 
     protected final int getMaxLowRowIndex() {
         int maxLowRowIndex = 0;
@@ -296,7 +301,7 @@
     /**
      * Prepend items until the low edge reaches downTo.
      */
-    public abstract void prependItems(int downTo);
+    public abstract void prependItems(int toLimit);
 
     /**
      * Strip items, keep a contiguous subset of items; the subset should include
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..a8b855a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
@@ -22,7 +22,7 @@
 final class StaggeredGridDefault extends StaggeredGrid {
 
     @Override
-    public void appendItems(int upTo) {
+    public void appendItems(int toLimit) {
         int count = mProvider.getCount();
         int itemIndex;
         int rowIndex;
@@ -36,9 +36,13 @@
 
     top_loop:
         while (true) {
-            // find highest row (.high is biggest)
-            int maxHighRowIndex = mLocations.size() > 0 ? getMaxHighRowIndex() : -1;
-            int maxHigh = maxHighRowIndex != -1 ? mRows[maxHighRowIndex].high : Integer.MIN_VALUE;
+            // find endmost row edge (.high is biggest, or .low is smallest in reversed flow)
+            int edgeRowIndex = mReversedFlow ?
+                    (mLocations.size() > 0 ? getMinLowRowIndex() : -1) :
+                    (mLocations.size() > 0 ? getMaxHighRowIndex() : -1);
+            int edge = mReversedFlow ?
+                    (edgeRowIndex != -1 ? mRows[edgeRowIndex].low : Integer.MAX_VALUE) :
+                    (edgeRowIndex != -1 ? mRows[edgeRowIndex].high : Integer.MIN_VALUE);
             // fill from current row till last row so that each row will grow longer than
             // the previous highest row.
             for (; rowIndex < mNumRows; rowIndex++) {
@@ -49,11 +53,15 @@
                 appendItemToRow(itemIndex++, rowIndex);
                 // fill more item to the row to make sure this row is longer than
                 // the previous highest row.
-                if (maxHighRowIndex == -1) {
-                    maxHighRowIndex = getMaxHighRowIndex();
-                    maxHigh = mRows[maxHighRowIndex].high;
-                } else  if (rowIndex != maxHighRowIndex) {
-                    while (mRows[rowIndex].high < maxHigh) {
+                if (edgeRowIndex == -1) {
+                    edgeRowIndex = mReversedFlow ? getMinLowRowIndex() : getMaxHighRowIndex();
+                    edge = mReversedFlow ?
+                            mRows[edgeRowIndex].low :
+                            mRows[edgeRowIndex].high;
+                } else  if (rowIndex != edgeRowIndex) {
+                    while (mReversedFlow ?
+                            mRows[rowIndex].low > edge :
+                            mRows[rowIndex].high < edge) {
                         if (itemIndex == count) {
                             break top_loop;
                         }
@@ -61,7 +69,9 @@
                     }
                 }
             }
-            if (mRows[getMinHighRowIndex()].high >= upTo) {
+            if (mReversedFlow ?
+                    mRows[getMaxLowRowIndex()].low <= toLimit :
+                    mRows[getMinHighRowIndex()].high >= toLimit) {
                 break;
             }
             // start fill from row 0 again
@@ -70,7 +80,7 @@
     }
 
     @Override
-    public void prependItems(int downTo) {
+    public void prependItems(int toLimit) {
         if (mProvider.getCount() <= 0) return;
         int itemIndex;
         int rowIndex;
@@ -89,18 +99,27 @@
 
     top_loop:
         while (true) {
-            int minLowRowIndex = mLocations.size() > 0 ? getMinLowRowIndex() : -1;
-            int minLow = minLowRowIndex != -1 ? mRows[minLowRowIndex].low : Integer.MAX_VALUE;
+            // find startmost row edge (.low is smallest, or .high is biggest in reversed flow)
+            int edgeRowIndex = mReversedFlow ?
+                    (mLocations.size() > 0 ? getMaxHighRowIndex() : -1) :
+                    (mLocations.size() > 0 ? getMinLowRowIndex() : -1);
+            int edge = mReversedFlow ?
+                    (edgeRowIndex != -1 ? mRows[edgeRowIndex].high : Integer.MIN_VALUE) :
+                    (edgeRowIndex != -1 ? mRows[edgeRowIndex].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 (edgeRowIndex == -1) {
+                    edgeRowIndex = mReversedFlow ? getMaxHighRowIndex() : getMinLowRowIndex();
+                    edge = mReversedFlow ?
+                            mRows[edgeRowIndex].high :
+                            mRows[edgeRowIndex].low;
+                } else if (rowIndex != edgeRowIndex) {
+                    while (mReversedFlow ?
+                            mRows[rowIndex].high < edge :
+                            mRows[rowIndex].low > edge) {
                         if (itemIndex < 0) {
                             break top_loop;
                         }
@@ -108,7 +127,9 @@
                     }
                 }
             }
-            if (mRows[getMaxLowRowIndex()].low <= downTo) {
+            if (mReversedFlow ?
+                    mRows[getMinHighRowIndex()].high >= toLimit :
+                    mRows[getMaxLowRowIndex()].low <= toLimit) {
                 break;
             }
             rowIndex = mNumRows - 1;
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/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index 79a2c1a..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/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/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
index ad343b7..6d00e0a 100644
--- a/v4/api21/android/support/v4/view/ViewCompatApi21.java
+++ b/v4/api21/android/support/v4/view/ViewCompatApi21.java
@@ -63,4 +63,8 @@
             }
         });
     }
+
+    public static boolean isImportantForAccessibility(View view) {
+        return view.isImportantForAccessibility();
+    }
 }
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/api22/android/support/v4/media/session/MediaSessionCompatApi22.java b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
new file mode 100644
index 0000000..b847778
--- /dev/null
+++ b/v4/api22/android/support/v4/media/session/MediaSessionCompatApi22.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.media.session.MediaSession;
+
+class MediaSessionCompatApi22 {
+
+    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..1cf63e8 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
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/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/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/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/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index 7116ee3..ee596f9 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -19,6 +19,7 @@
 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;
@@ -28,6 +29,7 @@
 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;
@@ -921,7 +923,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 +954,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/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 9b07694..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/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index d883e00..aaabf04 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -23,6 +23,7 @@
 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;
@@ -261,7 +262,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 +272,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 +332,11 @@
         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);
+        void setSaveFromParentEnabled(View view, boolean enabled);
+        void setActivated(View view, boolean activated);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -399,6 +404,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 +723,11 @@
         }
 
         @Override
+        public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+            // noop
+        }
+
+        @Override
         public void jumpDrawablesToCurrentState(View view) {
             // Do nothing; API didn't exist.
         }
@@ -724,6 +737,16 @@
                 OnApplyWindowInsetsListener listener) {
             // noop
         }
+
+        @Override
+        public void setSaveFromParentEnabled(View v, boolean enabled) {
+            // noop
+        }
+
+        @Override
+        public void setActivated(View view, boolean activated) {
+            // noop
+        }
     }
 
     static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -893,6 +916,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 +952,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 +994,11 @@
             }
             return vpa;
         }
+
+        @Override
+        public void setFitsSystemWindows(View view, boolean fitSystemWindows) {
+            ViewCompatICS.setFitsSystemWindows(view, fitSystemWindows);
+        }
     }
 
     static class JBViewCompatImpl extends ICSViewCompatImpl {
@@ -1144,6 +1184,11 @@
         public void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener) {
             ViewCompatApi21.setOnApplyWindowInsetsListener(view, listener);
         }
+
+        @Override
+        public boolean isImportantForAccessibility(View view) {
+            return ViewCompatApi21.isImportantForAccessibility(view);
+        }
     }
 
     static final ViewCompatImpl IMPL;
@@ -2243,6 +2288,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 +2317,29 @@
         IMPL.setOnApplyWindowInsetsListener(v, listener);
     }
 
+    /**
+     * 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);
+    }
+
     // TODO: getters for various view properties (rotation, etc)
 }
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/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..8204ec7 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();
         }
     }
 
@@ -995,6 +1015,15 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
      * Set a drawable to draw in the insets area for the status bar.
      * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
      *
@@ -1116,9 +1145,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 +1295,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 +1532,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 +1547,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 +1827,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 +1887,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 +1906,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 +1943,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/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/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index 2496ef9..2e056e6 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -101,6 +101,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 +447,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 +595,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.
      */
@@ -615,11 +644,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 +661,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 +764,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 +883,7 @@
             targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
             int offset = targetTop - mCircleView.getTop();
             setTargetOffsetTopAndBottom(offset, false /* requires update */);
+            mProgress.setArrowScale(1 - interpolatedTime);
         }
     };
 
@@ -920,4 +952,4 @@
     public interface OnRefreshListener {
         public void onRefresh();
     }
-}
\ No newline at end of file
+}
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-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/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/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_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/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..b07da0c
--- /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_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..51a895d
--- /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..2f59488
--- /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_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
index 0eacedd..06e7a17 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-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..f31730d
--- /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_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..d38aed2
--- /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..87dade3
--- /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_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
index e0d5ac4..2cabe7b 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-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..d675993
--- /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..8da42fe
--- /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..8337ffe
--- /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_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..33ec44c
--- /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..0166d70
--- /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_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
index 7accf52..0750fcc 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-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..469f736
--- /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_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..4b49faf
--- /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..561d9ef
--- /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_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
index 66f7d16..7e181d1 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-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..279afe3
--- /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/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_ratingbar_full_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
new file mode 100644
index 0000000..535e2da
--- /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_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..563258e 100644
--- a/v7/appcompat/res/values-af/strings.xml
+++ b/v7/appcompat/res/values-af/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-am/strings.xml b/v7/appcompat/res/values-am/strings.xml
index dbd53d6..8621f43 100644
--- a/v7/appcompat/res/values-am/strings.xml
+++ b/v7/appcompat/res/values-am/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ar/strings.xml b/v7/appcompat/res/values-ar/strings.xml
index 84d6fba..1f9167c 100644
--- a/v7/appcompat/res/values-ar/strings.xml
+++ b/v7/appcompat/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-bg/strings.xml b/v7/appcompat/res/values-bg/strings.xml
index 9d87ef7..e35b172 100644
--- a/v7/appcompat/res/values-bg/strings.xml
+++ b/v7/appcompat/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index ee522c6..79b78ca 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ca/strings.xml b/v7/appcompat/res/values-ca/strings.xml
index 5fe4b0d..d989428 100644
--- a/v7/appcompat/res/values-ca/strings.xml
+++ b/v7/appcompat/res/values-ca/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-cs/strings.xml b/v7/appcompat/res/values-cs/strings.xml
index 13c9ba8..aff38a1 100644
--- a/v7/appcompat/res/values-cs/strings.xml
+++ b/v7/appcompat/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-da/strings.xml b/v7/appcompat/res/values-da/strings.xml
index 03fec32..c06c2e9 100644
--- a/v7/appcompat/res/values-da/strings.xml
+++ b/v7/appcompat/res/values-da/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-de/strings.xml b/v7/appcompat/res/values-de/strings.xml
index 8a0224c..3ac2579 100644
--- a/v7/appcompat/res/values-de/strings.xml
+++ b/v7/appcompat/res/values-de/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-el/strings.xml b/v7/appcompat/res/values-el/strings.xml
index 52d1b81..f0acb4d 100644
--- a/v7/appcompat/res/values-el/strings.xml
+++ b/v7/appcompat/res/values-el/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-en-rGB/strings.xml b/v7/appcompat/res/values-en-rGB/strings.xml
index 8a8a111..f0f3beb 100644
--- a/v7/appcompat/res/values-en-rGB/strings.xml
+++ b/v7/appcompat/res/values-en-rGB/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-en-rIN/strings.xml b/v7/appcompat/res/values-en-rIN/strings.xml
index 8a8a111..f0f3beb 100644
--- a/v7/appcompat/res/values-en-rIN/strings.xml
+++ b/v7/appcompat/res/values-en-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml
index ea5004c..3be6353 100644
--- a/v7/appcompat/res/values-es-rUS/strings.xml
+++ b/v7/appcompat/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-es/strings.xml b/v7/appcompat/res/values-es/strings.xml
index c50796e..fc51934 100644
--- a/v7/appcompat/res/values-es/strings.xml
+++ b/v7/appcompat/res/values-es/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-et-rEE/strings.xml b/v7/appcompat/res/values-et-rEE/strings.xml
index 139fcf9..79b7036 100644
--- a/v7/appcompat/res/values-et-rEE/strings.xml
+++ b/v7/appcompat/res/values-et-rEE/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-eu-rES/strings.xml b/v7/appcompat/res/values-eu-rES/strings.xml
index 541c2ed..38b0018 100644
--- a/v7/appcompat/res/values-eu-rES/strings.xml
+++ b/v7/appcompat/res/values-eu-rES/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-fa/strings.xml b/v7/appcompat/res/values-fa/strings.xml
index c317bda..a813372 100644
--- a/v7/appcompat/res/values-fa/strings.xml
+++ b/v7/appcompat/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-fi/strings.xml b/v7/appcompat/res/values-fi/strings.xml
index 218229b..dc56b79 100644
--- a/v7/appcompat/res/values-fi/strings.xml
+++ b/v7/appcompat/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-fr-rCA/strings.xml b/v7/appcompat/res/values-fr-rCA/strings.xml
index 571ff9a..d401a01 100644
--- a/v7/appcompat/res/values-fr-rCA/strings.xml
+++ b/v7/appcompat/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-fr/strings.xml b/v7/appcompat/res/values-fr/strings.xml
index 353665a..5cb6f18 100644
--- a/v7/appcompat/res/values-fr/strings.xml
+++ b/v7/appcompat/res/values-fr/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-gl-rES/strings.xml b/v7/appcompat/res/values-gl-rES/strings.xml
index 3f665ed..76ce675 100644
--- a/v7/appcompat/res/values-gl-rES/strings.xml
+++ b/v7/appcompat/res/values-gl-rES/strings.xml
@@ -20,6 +20,7 @@
     <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>
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 23cfaca..7287d1a 100644
--- a/v7/appcompat/res/values-hi/strings.xml
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -17,9 +17,10 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="abc_action_mode_done" msgid="4076576682505996667">"पूर्ण"</string>
-    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुखपृष्ठ पर नेविगेट करें"</string>
+    <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>
diff --git a/v7/appcompat/res/values-hr/strings.xml b/v7/appcompat/res/values-hr/strings.xml
index 0348596..7ee2a03 100644
--- a/v7/appcompat/res/values-hr/strings.xml
+++ b/v7/appcompat/res/values-hr/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-hu/strings.xml b/v7/appcompat/res/values-hu/strings.xml
index fc67f00..4f9294a 100644
--- a/v7/appcompat/res/values-hu/strings.xml
+++ b/v7/appcompat/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml
index da67fe4..b75fe59 100644
--- a/v7/appcompat/res/values-hy-rAM/strings.xml
+++ b/v7/appcompat/res/values-hy-rAM/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-in/strings.xml b/v7/appcompat/res/values-in/strings.xml
index 3c31755..b25f01f 100644
--- a/v7/appcompat/res/values-in/strings.xml
+++ b/v7/appcompat/res/values-in/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-is-rIS/strings.xml b/v7/appcompat/res/values-is-rIS/strings.xml
index 7846b51..83baf81 100644
--- a/v7/appcompat/res/values-is-rIS/strings.xml
+++ b/v7/appcompat/res/values-is-rIS/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-it/strings.xml b/v7/appcompat/res/values-it/strings.xml
index 6ed52be..21802b3 100644
--- a/v7/appcompat/res/values-it/strings.xml
+++ b/v7/appcompat/res/values-it/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-iw/strings.xml b/v7/appcompat/res/values-iw/strings.xml
index fec0e62..08c814e 100644
--- a/v7/appcompat/res/values-iw/strings.xml
+++ b/v7/appcompat/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ja/strings.xml b/v7/appcompat/res/values-ja/strings.xml
index 181dd5e..d74c342 100644
--- a/v7/appcompat/res/values-ja/strings.xml
+++ b/v7/appcompat/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ka-rGE/strings.xml b/v7/appcompat/res/values-ka-rGE/strings.xml
index a96f26c..7aeeaea 100644
--- a/v7/appcompat/res/values-ka-rGE/strings.xml
+++ b/v7/appcompat/res/values-ka-rGE/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk-rKZ/strings.xml
index fb20a00..3bc7ec0 100644
--- a/v7/appcompat/res/values-kk-rKZ/strings.xml
+++ b/v7/appcompat/res/values-kk-rKZ/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-km-rKH/strings.xml b/v7/appcompat/res/values-km-rKH/strings.xml
index 704f4ee..32b8f68 100644
--- a/v7/appcompat/res/values-km-rKH/strings.xml
+++ b/v7/appcompat/res/values-km-rKH/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -27,7 +28,7 @@
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"សម្អាត​សំណួរ"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ដាក់​​​ស្នើ​សំណួរ"</string>
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"ការស្វែងរក​សំឡេង"</string>
-    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ជ្រើស​កម្មវិធី​​"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ជ្រើស​កម្មវិធី"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"មើល​ទាំងអស់"</string>
     <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"ចែករំលែក​ជាមួយ %s"</string>
     <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ចែករំលែក​ជាមួយ"</string>
diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn-rIN/strings.xml
index d472ff3..5a93d5a 100644
--- a/v7/appcompat/res/values-kn-rIN/strings.xml
+++ b/v7/appcompat/res/values-kn-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ko/strings.xml b/v7/appcompat/res/values-ko/strings.xml
index 0a92a83..9955a58 100644
--- a/v7/appcompat/res/values-ko/strings.xml
+++ b/v7/appcompat/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ky-rKG/strings.xml b/v7/appcompat/res/values-ky-rKG/strings.xml
index b091f60..c3cc6f7 100644
--- a/v7/appcompat/res/values-ky-rKG/strings.xml
+++ b/v7/appcompat/res/values-ky-rKG/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-lo-rLA/strings.xml b/v7/appcompat/res/values-lo-rLA/strings.xml
index 33614e6..cc4170f 100644
--- a/v7/appcompat/res/values-lo-rLA/strings.xml
+++ b/v7/appcompat/res/values-lo-rLA/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-lt/strings.xml b/v7/appcompat/res/values-lt/strings.xml
index 3c992a7..e5d8975 100644
--- a/v7/appcompat/res/values-lt/strings.xml
+++ b/v7/appcompat/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-lv/strings.xml b/v7/appcompat/res/values-lv/strings.xml
index 3bd7259..9f1a0f0 100644
--- a/v7/appcompat/res/values-lv/strings.xml
+++ b/v7/appcompat/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-mk-rMK/strings.xml b/v7/appcompat/res/values-mk-rMK/strings.xml
index b1abf10..5df2278 100644
--- a/v7/appcompat/res/values-mk-rMK/strings.xml
+++ b/v7/appcompat/res/values-mk-rMK/strings.xml
@@ -20,6 +20,7 @@
     <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 />
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
index f7512ad..3a0f82c 100644
--- a/v7/appcompat/res/values-ml-rIN/strings.xml
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -20,13 +20,14 @@
     <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_searchview_description_query" msgid="2550479030709304392">"തിരയൽ അന്വേഷണം"</string>
     <string name="abc_searchview_description_clear" msgid="3691816814315814921">"അന്വേഷണം മായ്‌ക്കുക"</string>
     <string name="abc_searchview_description_submit" msgid="8928215447528550784">"അന്വേഷണം സമർപ്പിക്കുക"</string>
-    <string name="abc_searchview_description_voice" msgid="893419373245838918">"വോയ്‌സ് തിരയൽ"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ശബ്ദ തിരയൽ"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ഒരു അപ്ലിക്കേഷൻ തിരഞ്ഞെടുക്കുക"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"എല്ലാം കാണുക"</string>
     <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s എന്നതുമായി പങ്കിടുക"</string>
diff --git a/v7/appcompat/res/values-mn-rMN/strings.xml b/v7/appcompat/res/values-mn-rMN/strings.xml
index a1a9c6f..073f0df 100644
--- a/v7/appcompat/res/values-mn-rMN/strings.xml
+++ b/v7/appcompat/res/values-mn-rMN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-mr-rIN/strings.xml b/v7/appcompat/res/values-mr-rIN/strings.xml
index 3d0e718..3177012 100644
--- a/v7/appcompat/res/values-mr-rIN/strings.xml
+++ b/v7/appcompat/res/values-mr-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ms-rMY/strings.xml b/v7/appcompat/res/values-ms-rMY/strings.xml
index d2886a1..d77560f 100644
--- a/v7/appcompat/res/values-ms-rMY/strings.xml
+++ b/v7/appcompat/res/values-ms-rMY/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my-rMM/strings.xml
index 3ac8472..38602a2 100644
--- a/v7/appcompat/res/values-my-rMM/strings.xml
+++ b/v7/appcompat/res/values-my-rMM/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-nb/strings.xml b/v7/appcompat/res/values-nb/strings.xml
index 3dbd071..0cf77a1 100644
--- a/v7/appcompat/res/values-nb/strings.xml
+++ b/v7/appcompat/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ne-rNP/strings.xml b/v7/appcompat/res/values-ne-rNP/strings.xml
index 0154662..dee552a 100644
--- a/v7/appcompat/res/values-ne-rNP/strings.xml
+++ b/v7/appcompat/res/values-ne-rNP/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-nl/strings.xml b/v7/appcompat/res/values-nl/strings.xml
index 330de8d..cd67586 100644
--- a/v7/appcompat/res/values-nl/strings.xml
+++ b/v7/appcompat/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-pl/strings.xml b/v7/appcompat/res/values-pl/strings.xml
index 8e32155..7245cad 100644
--- a/v7/appcompat/res/values-pl/strings.xml
+++ b/v7/appcompat/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-pt-rPT/strings.xml b/v7/appcompat/res/values-pt-rPT/strings.xml
index e1c622e..8d05c98 100644
--- a/v7/appcompat/res/values-pt-rPT/strings.xml
+++ b/v7/appcompat/res/values-pt-rPT/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-pt/strings.xml b/v7/appcompat/res/values-pt/strings.xml
index abdd650..4fecc5c 100644
--- a/v7/appcompat/res/values-pt/strings.xml
+++ b/v7/appcompat/res/values-pt/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ro/strings.xml b/v7/appcompat/res/values-ro/strings.xml
index 6dd2b67..ea1ecbd 100644
--- a/v7/appcompat/res/values-ro/strings.xml
+++ b/v7/appcompat/res/values-ro/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ru/strings.xml b/v7/appcompat/res/values-ru/strings.xml
index 9c5ed89..3042ce6 100644
--- a/v7/appcompat/res/values-ru/strings.xml
+++ b/v7/appcompat/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-si-rLK/strings.xml b/v7/appcompat/res/values-si-rLK/strings.xml
index 22809d6..6fb7d6a 100644
--- a/v7/appcompat/res/values-si-rLK/strings.xml
+++ b/v7/appcompat/res/values-si-rLK/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-sk/strings.xml b/v7/appcompat/res/values-sk/strings.xml
index 2b09cce..f48351f 100644
--- a/v7/appcompat/res/values-sk/strings.xml
+++ b/v7/appcompat/res/values-sk/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-sl/strings.xml b/v7/appcompat/res/values-sl/strings.xml
index a522de2..3e8c881 100644
--- a/v7/appcompat/res/values-sl/strings.xml
+++ b/v7/appcompat/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-sr/strings.xml b/v7/appcompat/res/values-sr/strings.xml
index c26945b..cb85060 100644
--- a/v7/appcompat/res/values-sr/strings.xml
+++ b/v7/appcompat/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-sv/strings.xml b/v7/appcompat/res/values-sv/strings.xml
index 3120ad8..be6827e 100644
--- a/v7/appcompat/res/values-sv/strings.xml
+++ b/v7/appcompat/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-sw/strings.xml b/v7/appcompat/res/values-sw/strings.xml
index afe54f6..7f07a2a 100644
--- a/v7/appcompat/res/values-sw/strings.xml
+++ b/v7/appcompat/res/values-sw/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ta-rIN/strings.xml b/v7/appcompat/res/values-ta-rIN/strings.xml
index 542fd34..d72fcc4 100644
--- a/v7/appcompat/res/values-ta-rIN/strings.xml
+++ b/v7/appcompat/res/values-ta-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-te-rIN/strings.xml b/v7/appcompat/res/values-te-rIN/strings.xml
index 5f36cc5..20fbbeb 100644
--- a/v7/appcompat/res/values-te-rIN/strings.xml
+++ b/v7/appcompat/res/values-te-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-th/strings.xml b/v7/appcompat/res/values-th/strings.xml
index d8c04c4..38b5c90 100644
--- a/v7/appcompat/res/values-th/strings.xml
+++ b/v7/appcompat/res/values-th/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-tl/strings.xml b/v7/appcompat/res/values-tl/strings.xml
index 0384435..079990f 100644
--- a/v7/appcompat/res/values-tl/strings.xml
+++ b/v7/appcompat/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
index c06069c..51ee6ec 100644
--- a/v7/appcompat/res/values-tr/strings.xml
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-uk/strings.xml b/v7/appcompat/res/values-uk/strings.xml
index d07404b..28f6b8e 100644
--- a/v7/appcompat/res/values-uk/strings.xml
+++ b/v7/appcompat/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-ur-rPK/strings.xml b/v7/appcompat/res/values-ur-rPK/strings.xml
index 89c0ea6..787651e 100644
--- a/v7/appcompat/res/values-ur-rPK/strings.xml
+++ b/v7/appcompat/res/values-ur-rPK/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
index 537afa1..033e4e0 100644
--- a/v7/appcompat/res/values-uz-rUZ/strings.xml
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -20,6 +20,7 @@
     <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>
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..6880321 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -66,6 +66,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>
@@ -117,6 +118,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>
@@ -167,6 +169,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>
@@ -218,6 +221,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>
@@ -308,7 +312,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>
@@ -317,6 +321,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>
@@ -326,9 +331,17 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_light</item>
+        <item name="colorButtonNormal">@color/button_material_light</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</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>
     </style>
 
     <style name="Base.Theme.AppCompat" parent="Base.V11.Theme.AppCompat">
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-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 648dfd2..6a352ef 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -112,6 +112,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 +164,20 @@
     <!-- 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" />
 
     <!-- Progress Bar -->
 
@@ -183,13 +189,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..c586030 100644
--- a/v7/appcompat/res/values-v21/themes_base.xml
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -200,11 +200,16 @@
     <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 +217,27 @@
     <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>
     </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>
+
         <!-- 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..28b93ab 100644
--- a/v7/appcompat/res/values-vi/strings.xml
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-zh-rCN/strings.xml b/v7/appcompat/res/values-zh-rCN/strings.xml
index 54e2c86..519abd2 100644
--- a/v7/appcompat/res/values-zh-rCN/strings.xml
+++ b/v7/appcompat/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-zh-rHK/strings.xml b/v7/appcompat/res/values-zh-rHK/strings.xml
index e35d465..4974223 100644
--- a/v7/appcompat/res/values-zh-rHK/strings.xml
+++ b/v7/appcompat/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-zh-rTW/strings.xml b/v7/appcompat/res/values-zh-rTW/strings.xml
index 24d530c..97f2589 100644
--- a/v7/appcompat/res/values-zh-rTW/strings.xml
+++ b/v7/appcompat/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values-zu/strings.xml b/v7/appcompat/res/values-zu/strings.xml
index a6a06ab..21ee299 100644
--- a/v7/appcompat/res/values-zu/strings.xml
+++ b/v7/appcompat/res/values-zu/strings.xml
@@ -20,6 +20,7 @@
     <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>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 896a80a..3fd90b2 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -70,6 +70,7 @@
         <attr name="windowFixedHeightMajor" format="dimension|fraction" />
 
         <attr name="android:windowIsFloating" />
+        <attr name="android:windowAnimationStyle" />
 
         <!-- =================== -->
         <!-- Action bar styles   -->
@@ -723,14 +724,10 @@
              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>
+        <!-- Icon drawable to use for the collapse button. -->
         <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 +814,10 @@
         <attr name="showText" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="SwitchCompatTextAppearance">
+        <attr name="android:textSize" />
+        <attr name="android:textColor" />
+        <attr name="textAllCaps" />
+    </declare-styleable>
+
 </resources>
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index a57f2e4..0f8b7dc 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -32,4 +32,8 @@
          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>
+
 </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..34a81a6 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -58,9 +58,17 @@
          (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>
 
 </resources>
diff --git a/v7/appcompat/res/values/strings.xml b/v7/appcompat/res/values/strings.xml
index 765833d..5080070 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.
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 1b8b53b..f80aae5 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -120,6 +120,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,18 +189,10 @@
            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.EditText"
@@ -207,6 +201,14 @@
 
     <style name="Widget.AppCompat.CompoundButton.Switch" parent="Base.Widget.AppCompat.CompoundButton.Switch" />
 
+    <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.TextView.SpinnerItem" parent="Base.Widget.AppCompat.TextView.SpinnerItem" />
+
     <!-- Toolbar -->
 
     <style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
@@ -272,6 +274,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 +295,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..fb28f4e 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -36,9 +36,6 @@
         <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="elevation">8dp</item>
         <item name="popupTheme">?attr/actionBarPopupTheme</item>
@@ -186,6 +183,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 +282,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 +306,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>
 
@@ -348,16 +342,20 @@
         <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.Switch" parent="android:Widget.CompoundButton">
@@ -370,4 +368,34 @@
 
     <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>
+
+    <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" />
+
 </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/themes.xml b/v7/appcompat/res/values/themes.xml
index 05b8657..3409c68 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -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="Animation.AppCompat.DropDownUp" parent="Base.Animation.AppCompat.DropDownUp" />
 
     <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..c151d3b 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -47,7 +47,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 +61,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>
@@ -107,6 +108,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>
@@ -138,7 +140,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 -->
@@ -152,6 +154,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>
@@ -260,6 +263,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 +273,19 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <item name="colorButtonNormal">@color/button_material_dark</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_dark</item>
 
         <item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</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>
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
@@ -359,7 +371,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 +380,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 +390,19 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_light</item>
+        <item name="colorButtonNormal">@color/button_material_light</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_normal_material_light</item>
 
         <item name="drawerArrowStyle">@style/Widget.AppCompat.DrawerArrowToggle</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>
     </style>
 
     <style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -406,6 +427,12 @@
     <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.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>
 
     <style name="Base.V7.Theme.AppCompat.Dialog" parent="Platform.AppCompat.Dialog">
@@ -497,6 +524,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>
@@ -506,9 +534,17 @@
         <item name="colorControlNormal">?android:attr/textColorSecondary</item>
         <item name="colorControlActivated">?attr/colorAccent</item>
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <item name="colorButtonNormal">@color/button_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: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>
     </style>
 
     <style name="Base.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
@@ -589,7 +625,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 -->
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index c174cb4..785b7b8 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;
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
index 5fa8cfb..2168d5e 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
@@ -124,6 +124,12 @@
     }
 
     @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         getDelegate().onConfigurationChanged(newConfig);
@@ -142,15 +148,6 @@
     }
 
     @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;
@@ -519,12 +516,11 @@
     }
 
     @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        // First let the Activity try and handle it (for back, etc)
-        if (super.onKeyDown(keyCode, event)) {
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (getDelegate().dispatchKeyEvent(event)) {
             return true;
         }
-        return getDelegate().onKeyDown(keyCode, event);
+        return super.dispatchKeyEvent(event);
     }
 
     /**
@@ -549,7 +545,7 @@
             return result;
         }
         // If we reach here super didn't create a View, so let our delegate attempt it
-        return getDelegate().createView(name, attrs);
+        return getDelegate().createView(name, context, attrs);
     }
 
     private ActionBarActivityDelegate getDelegate() {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
index 855f9e6..9599adc 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
@@ -29,6 +29,7 @@
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.view.SupportMenuInflater;
+import android.support.v7.internal.widget.TintTypedArray;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.Toolbar;
 import android.util.AttributeSet;
@@ -101,7 +102,7 @@
 
         @Override
         public View onCreatePanelView(int featureId) {
-            return null;
+            return mActivity.onCreatePanelView(featureId);
         }
     };
     // The fake window callback we're currently using
@@ -126,6 +127,10 @@
         return mActionBar;
     }
 
+    final ActionBar peekSupportActionBar() {
+        return mActionBar;
+    }
+
     protected final void setSupportActionBar(ActionBar actionBar) {
         mActionBar = actionBar;
     }
@@ -148,13 +153,21 @@
                     "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);
+        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);
         a.recycle();
     }
 
+    abstract void onPostCreate(Bundle savedInstanceState);
+
     abstract void onConfigurationChanged(Configuration newConfig);
 
     abstract void onStop();
@@ -176,8 +189,6 @@
     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);
@@ -208,9 +219,7 @@
 
     abstract void setSupportProgress(int progress);
 
-    boolean onKeyDown(int keyCode, KeyEvent event) {
-        return false;
-    }
+    abstract boolean dispatchKeyEvent(KeyEvent event);
 
     abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
 
@@ -259,16 +268,15 @@
         return context;
     }
 
-    abstract View createView(String name, @NonNull AttributeSet attrs);
-
+    abstract View createView(String name, @NonNull Context context, @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 TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+                    getActionBarThemedContext(), null, new int[]{ getHomeAsUpIndicatorAttrId() });
             final Drawable result = a.getDrawable(0);
             a.recycle();
             return result;
@@ -280,6 +288,12 @@
         }
 
         @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) {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
index 6a4f9ba..0c7d7c9 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
@@ -20,7 +20,9 @@
 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;
@@ -32,7 +34,6 @@
 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.ToolbarActionBar;
 import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.app.WindowDecorActionBar;
@@ -44,18 +45,24 @@
 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.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.TintManager;
+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.ViewStubCompat;
 import android.support.v7.internal.widget.ViewUtils;
 import android.support.v7.view.ActionMode;
 import android.support.v7.widget.Toolbar;
+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;
@@ -64,10 +71,13 @@
 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;
@@ -75,6 +85,8 @@
 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
@@ -125,8 +137,6 @@
 
     private boolean mEnableDefaultActionBarUp;
 
-    private ListMenuPresenter mToolbarListMenuPresenter;
-
     private Rect mTempRect1;
     private Rect mTempRect2;
 
@@ -141,7 +151,8 @@
         mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView();
 
         if (NavUtils.getParentActivityName(mActivity) != null) {
-            ActionBar ab = getSupportActionBar();
+            // Peek at the Action Bar and update it if it already exists
+            ActionBar ab = peekSupportActionBar();
             if (ab == null) {
                 mEnableDefaultActionBarUp = true;
             } else {
@@ -151,6 +162,12 @@
     }
 
     @Override
+    void onPostCreate(Bundle savedInstanceState) {
+        // Make sure that the sub decor is installed
+        ensureSubDecor();
+    }
+
+    @Override
     public ActionBar createSupportActionBar() {
         ensureSubDecor();
         ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar);
@@ -165,17 +182,12 @@
             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);
         setSupportActionBar(tbab);
         setWindowCallback(tbab.getWrappedWindowCallback());
         tbab.invalidateOptionsMenu();
@@ -335,14 +347,24 @@
             // Make the decor optionally fit system windows, like the window's decor
             ViewUtils.makeOptionalFitsSystemWindows(mSubDecor);
 
+            final ViewGroup decorContent = (ViewGroup) mActivity.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 Activity's content view with the decor
             mActivity.superSetContentView(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).
@@ -436,23 +458,28 @@
     public boolean supportRequestWindowFeature(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 mActivity.requestWindowFeature(featureId);
     }
 
     @Override
@@ -467,33 +494,6 @@
     }
 
     @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);
@@ -511,12 +511,6 @@
 
     @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);
-        }
-
         if (featureId == FEATURE_ACTION_BAR) {
             ActionBar ab = getSupportActionBar();
             if (ab != null) {
@@ -683,25 +677,22 @@
 
     @Override
     void setSupportProgressBarVisibility(boolean visible) {
-        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
-                Window.PROGRESS_VISIBILITY_OFF);
+        // noop
     }
 
     @Override
     void setSupportProgressBarIndeterminateVisibility(boolean visible) {
-        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
-                Window.PROGRESS_VISIBILITY_OFF);
+        // noop
     }
 
     @Override
     void setSupportProgressBarIndeterminate(boolean indeterminate) {
-        updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON
-                : Window.PROGRESS_INDETERMINATE_OFF);
+        // noop
     }
 
     @Override
     void setSupportProgress(int progress) {
-        updateProgressBars(Window.PROGRESS_START + progress);
+        // noop
     }
 
     @Override
@@ -711,6 +702,12 @@
 
     @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,85 +738,73 @@
     }
 
     @Override
-    boolean onKeyDown(int keyCode, KeyEvent event) {
+    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);
+    }
+
+    protected 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;
+                }
+                break;
+        }
+        return false;
+    }
+
+    protected boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == 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) {
+    View createView(final String name, @NonNull Context context, @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);
+                    return new TintEditText(context, attrs);
                 case "Spinner":
-                    return new TintSpinner(mActivity, attrs);
+                    return new TintSpinner(context, attrs);
                 case "CheckBox":
-                    return new TintCheckBox(mActivity, attrs);
+                    return new TintCheckBox(context, attrs);
                 case "RadioButton":
-                    return new TintRadioButton(mActivity, attrs);
+                    return new TintRadioButton(context, attrs);
                 case "CheckedTextView":
-                    return new TintCheckedTextView(mActivity, attrs);
+                    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);
             }
         }
         return null;
     }
 
-    /**
-     * Progress Bar function. Mostly extracted from PhoneWindow.java
-     */
-    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);
-            }
-        }
-    }
-
-    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);
-        }
-    }
-
     private void openPanel(final PanelFeatureState st, KeyEvent event) {
         // Already open, return
         if (st.isOpen || isDestroyed()) {
@@ -848,27 +833,83 @@
             return;
         }
 
+        final WindowManager wm = (WindowManager) mActivity.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) {
@@ -876,7 +917,7 @@
                 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) ||
                         mDecorContentParent.isOverflowMenuShowPending())) {
 
-            WindowCallback cb = getWindowCallback();
+            final WindowCallback cb = getWindowCallback();
 
             if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
                 if (cb != null && !isDestroyed()) {
@@ -892,7 +933,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 +942,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,43 +956,6 @@
         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;
 
@@ -996,6 +1000,11 @@
     }
 
     private boolean initializePanelContent(PanelFeatureState st) {
+        if (st.createdPanelView != null) {
+            st.shownPanelView = st.createdPanelView;
+            return true;
+        }
+
         if (st.menu == null) {
             return false;
         }
@@ -1026,6 +1035,12 @@
             closePanel(mPreparedPanel, false);
         }
 
+        final WindowCallback 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 +1050,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 (!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);
+                    }
+
+                    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;
@@ -1119,6 +1136,10 @@
         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,7 +1147,12 @@
             return;
         }
 
-        if (st.isOpen) {
+        final WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
+        if ((wm != null) && st.isOpen) {
+            if (st.decorView != null) {
+                wm.removeView(st.decorView);
+            }
+
             if (doCallback) {
                 callOnPanelClosed(st.featureId, st, null);
             }
@@ -1148,6 +1174,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(mActivity))) {
+            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) mActivity.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) {
@@ -1337,19 +1430,10 @@
         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(
+                    "supportRequestWindowFeature() must be called before adding content");
         }
     }
 
@@ -1452,12 +1536,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 +1604,7 @@
 
         public boolean hasPanelItems() {
             if (shownPanelView == null) return false;
+            if (createdPanelView != null) return true;
 
             return listMenuPresenter.getAdapter().getCount() > 0;
         }
@@ -1544,6 +1642,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 +1751,38 @@
         }
     }
 
+    private class ListMenuDecorView extends FrameLayout {
+        public ListMenuDecorView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            return ActionBarActivityDelegateBase.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/ActionBarActivityDelegateHC.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
index 0adda0d..97f2b22 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
@@ -47,12 +47,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) {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java b/v7/appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java
index 7edab96..93b6a66 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;
@@ -112,6 +113,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 +132,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.
@@ -449,6 +459,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 +546,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 +600,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 +630,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 +699,10 @@
         public Context getActionBarThemedContext() {
             return mActivity;
         }
+
+        @Override
+        public boolean isNavigationVisible() {
+            return true;
+        }
     }
 }
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/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
index 08f8552..5c63889 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
@@ -50,7 +50,6 @@
  * @hide
  */
 public class ToolbarActionBar extends ActionBar {
-    private Toolbar mToolbar;
     private DecorToolbar mDecorToolbar;
     private boolean mToolbarMenuPrepared;
     private WindowCallback mWindowCallback;
@@ -80,7 +79,6 @@
 
     public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window,
             WindowCallback windowCallback) {
-        mToolbar = toolbar;
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
         mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
         mDecorToolbar.setWindowCallback(mWindowCallback);
@@ -107,8 +105,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 +146,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 +166,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
@@ -296,7 +294,7 @@
 
     @Override
     public void setBackgroundDrawable(@Nullable Drawable d) {
-        mToolbar.setBackgroundDrawable(d);
+        mDecorToolbar.setBackgroundDrawable(d);
     }
 
     @Override
@@ -306,12 +304,12 @@
 
     @Override
     public CharSequence getTitle() {
-        return mToolbar.getTitle();
+        return mDecorToolbar.getTitle();
     }
 
     @Override
     public CharSequence getSubtitle() {
-        return mToolbar.getSubtitle();
+        return mDecorToolbar.getSubtitle();
     }
 
     @Override
@@ -405,44 +403,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 +473,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,16 +500,45 @@
     }
 
     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) {
             super(wrapped);
@@ -525,20 +558,11 @@
         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 (mWindowCallback != null &&
+                            mWindowCallback.onPreparePanel(featureId, null, menu) &&
+                            mWindowCallback.onMenuOpened(featureId, menu)) {
+                        return getListMenuView(menu);
                     }
                     break;
             }
@@ -548,31 +572,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 +598,7 @@
             }
 
             mClosingActionMenu = true;
-            mToolbar.dismissPopupMenus();
+            mDecorToolbar.dismissPopupMenus();
             if (mWindowCallback != null) {
                 mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu);
             }
@@ -632,7 +636,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/WindowDecorActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
index 96ed48f..6eccb1d 100644
--- a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
+++ b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
@@ -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);
@@ -944,20 +944,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
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/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..7435874 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/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/DecorToolbar.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
index 67422d5..dfeb34c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
@@ -21,6 +21,7 @@
 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;
@@ -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/DrawableWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
index 6a1711e..ad15064 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DrawableWrapper.java
@@ -35,11 +35,10 @@
  */
 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
@@ -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/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/TintAutoCompleteTextView.java b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
new file mode 100644
index 0000000..92c1b40
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintAutoCompleteTextView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.widget.AutoCompleteTextView;
+import android.widget.EditText;
+
+/**
+ * An tint aware {@link android.widget.AutoCompleteTextView}.
+ *
+ * @hide
+ */
+public class TintAutoCompleteTextView extends AutoCompleteTextView {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background,
+            android.R.attr.popupBackground
+    };
+
+    private final TintManager mTintManager;
+
+    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(context, attrs, defStyleAttr);
+
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+                defStyleAttr, 0);
+        setBackgroundDrawable(a.getDrawable(0));
+        if (a.hasValue(1)) {
+            setDropDownBackgroundDrawable(a.getDrawable(1));
+        }
+        a.recycle();
+
+        mTintManager = a.getTintManager();
+    }
+
+    @Override
+    public void setDropDownBackgroundResource(int id) {
+        setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+    }
+}
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..fc822cd
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintButton.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * An tint aware {@link android.widget.Button}
+ *
+ * @hide
+ */
+public class TintButton extends Button {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background,
+            android.R.attr.textAppearance
+    };
+
+    private final TintManager mTintManager;
+
+    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);
+
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+                defStyleAttr, 0);
+        if (a.hasValue(0)) {
+            setBackgroundDrawable(a.getDrawable(0));
+        }
+
+        // Keep the TintManager in case we need it later
+        mTintManager = a.getTintManager();
+    }
+
+    @Override
+    public void setBackgroundResource(int resid) {
+        setBackgroundDrawable(mTintManager.getDrawable(resid));
+    }
+}
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..52e3206 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintManager.java
@@ -59,7 +59,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
     };
 
     /**
@@ -92,7 +93,9 @@
             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
     };
 
     /**
@@ -110,6 +113,7 @@
     private ColorStateList mDefaultColorStateList;
     private ColorStateList mSwitchThumbStateList;
     private ColorStateList mSwitchTrackStateList;
+    private ColorStateList mButtonStateList;
 
     /**
      * A helper method to instantiate a {@link TintManager} and then call {@link #getDrawable(int)}.
@@ -133,6 +137,8 @@
         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) {
@@ -140,6 +146,8 @@
             } else if (resId == R.drawable.abc_switch_thumb_material) {
                 drawable = new TintDrawableWrapper(drawable, getSwitchThumbColorStateList(),
                         PorterDuff.Mode.MULTIPLY);
+            } else if (resId == R.drawable.abc_btn_default_mtrl_shape) {
+                drawable = new TintDrawableWrapper(drawable, getButtonColorStateList());
             } else if (arrayContains(CONTAINERS_WITH_TINT_CHILDREN, resId)) {
                 drawable = mResources.getDrawable(resId);
             } else {
@@ -316,6 +324,35 @@
         return mSwitchThumbStateList;
     }
 
+    private ColorStateList getButtonColorStateList() {
+        if (mButtonStateList == null) {
+            final int[][] states = new int[4][];
+            final int[] colors = new int[4];
+            int i = 0;
+
+            // Disabled state
+            states[i] = new int[] { -android.R.attr.state_enabled };
+            colors[i] = getDisabledThemeAttrColor(R.attr.colorButtonNormal);
+            i++;
+
+            states[i] = new int[] { android.R.attr.state_pressed };
+            colors[i] = getThemeAttrColor(R.attr.colorControlHighlight);
+            i++;
+
+            states[i] = new int[] { android.R.attr.state_focused };
+            colors[i] = getThemeAttrColor(R.attr.colorControlHighlight);
+            i++;
+
+            // Default enabled state
+            states[i] = new int[0];
+            colors[i] = getThemeAttrColor(R.attr.colorButtonNormal);
+            i++;
+
+            mButtonStateList = new ColorStateList(states, colors);
+        }
+        return mButtonStateList;
+    }
+
     int getThemeAttrColor(int attr) {
         if (mContext.getTheme().resolveAttribute(attr, mTypedValue, true)) {
             if (mTypedValue.type >= TypedValue.TYPE_FIRST_INT
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..9746335
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintMultiAutoCompleteTextView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.AutoCompleteTextView;
+import android.widget.MultiAutoCompleteTextView;
+
+/**
+ * An tint aware {@link android.widget.MultiAutoCompleteTextView}.
+ *
+ * @hide
+ */
+public class TintMultiAutoCompleteTextView extends MultiAutoCompleteTextView {
+
+    private static final int[] TINT_ATTRS = {
+            android.R.attr.background,
+            android.R.attr.popupBackground
+    };
+
+    private final TintManager mTintManager;
+
+    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(context, attrs, defStyleAttr);
+
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, TINT_ATTRS,
+                defStyleAttr, 0);
+        setBackgroundDrawable(a.getDrawable(0));
+        if (a.hasValue(1)) {
+            setDropDownBackgroundDrawable(a.getDrawable(1));
+        }
+        a.recycle();
+
+        mTintManager = a.getTintManager();
+    }
+
+    @Override
+    public void setDropDownBackgroundResource(int id) {
+        setDropDownBackgroundDrawable(mTintManager.getDrawable(id));
+    }
+
+}
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..592c6ff
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintRatingBar.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.RatingBar;
+
+/**
+ * An tint aware {@link android.widget.RatingBar}.
+ *
+ * @hide
+ */
+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);
+
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, 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..3f81d35 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/TintSpinner.java
@@ -16,12 +16,16 @@
 
 package android.support.v7.internal.widget;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 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}.
  *
@@ -49,11 +53,34 @@
                 defStyleAttr, 0);
         setBackgroundDrawable(a.getDrawable(0));
 
-        if (Build.VERSION.SDK_INT >= 16 && a.hasValue(1)) {
-            setPopupBackgroundDrawable(a.getDrawable(1));
+        if (a.hasValue(1)) {
+            final Drawable background = a.getDrawable(1);
+            if (Build.VERSION.SDK_INT >= 16) {
+                setPopupBackgroundDrawable(background);
+            } else if (Build.VERSION.SDK_INT >= 11) {
+                setPopupBackgroundDrawableV11(this, background);
+            }
         }
 
         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();
+        }
+    }
+
 }
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..ff22b16 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
@@ -95,6 +95,7 @@
         mTitle = toolbar.getTitle();
         mSubtitle = toolbar.getSubtitle();
         mTitleSet = mTitle != null;
+        mNavIcon = mToolbar.getNavigationIcon();
 
         if (style) {
             final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
@@ -676,4 +677,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/widget/ActionMenuView.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
index d1f83d4..563961a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
@@ -439,7 +439,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..01d5e1c 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(
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 14813b1..5b7d333 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..46ad229 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;
@@ -1605,17 +1606,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 +1622,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 +1634,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 e894f49..74d6e4a 100644
--- a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
@@ -44,6 +44,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,12 +76,6 @@
     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
-    };
-
     // Enum for the "typeface" XML parameter.
     private static final int SANS = 1;
     private static final int SERIF = 2;
@@ -228,12 +223,14 @@
      * 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.SwitchCompatTextAppearance);
 
         ColorStateList colors;
         int ts;
 
-        colors = appearance.getColorStateList(0);
+        colors = appearance.getColorStateList(
+                R.styleable.SwitchCompatTextAppearance_android_textColor);
         if (colors != null) {
             mTextColors = colors;
         } else {
@@ -241,7 +238,8 @@
             mTextColors = getTextColors();
         }
 
-        ts = appearance.getDimensionPixelSize(1, 0);
+        ts = appearance.getDimensionPixelSize(
+                R.styleable.SwitchCompatTextAppearance_android_textSize, 0);
         if (ts != 0) {
             if (ts != mTextPaint.getTextSize()) {
                 mTextPaint.setTextSize(ts);
@@ -249,7 +247,8 @@
             }
         }
 
-        boolean allCaps = appearance.getBoolean(2, false);
+        boolean allCaps = appearance.getBoolean(
+                R.styleable.SwitchCompatTextAppearance_textAllCaps, false);
         if (allCaps) {
             mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
         } else {
@@ -685,6 +684,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);
@@ -695,10 +695,13 @@
                 newState = getTargetCheckedState();
             }
         } else {
-            newState = isChecked();
+            newState = oldState;
         }
 
-        setChecked(newState);
+        if (newState != oldState) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            setChecked(newState);
+        }
         cancelSuperTouch(ev);
     }
 
@@ -990,7 +993,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);
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index 2330b04..d98aec7 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -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>();
@@ -200,7 +202,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 +248,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 +1004,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 +1099,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();
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..4a3396a
--- /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..440751e
--- /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..20740bc
--- /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..c6f9002
--- /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..9d7a4e4
--- /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..679bb98
--- /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..2910903
--- /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..b7e6db8
--- /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..1d465a4
--- /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..2746d17
--- /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..07603b5
--- /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..27e2d4a
--- /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..3afde73
--- /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..f4ce1d5
--- /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..8dcb6e9
--- /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..5584a13
--- /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..337cf0f
--- /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..ec70ee1
--- /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..b36cf49
--- /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..2046149
--- /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..66efe42
--- /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..a1f4101
--- /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_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..9bee201
--- /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..b60a666
--- /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..6b7885c
--- /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..0de509a
--- /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..6d94fee
--- /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..af23356
--- /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..8b238d4
--- /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..13c53e7
--- /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..3e6b2a1
--- /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..7966bbc
--- /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..a2c68c5
--- /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..69287b8
--- /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..cf035c0
--- /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..247b72f
--- /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..eaed2f7
--- /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..77bd8f5
--- /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..0842337
--- /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..affca8b
--- /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..33f08ae
--- /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..4360c45
--- /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..28fbc90
--- /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..5625eed
--- /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_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..c3649df
--- /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..fcb2139
--- /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..ed30868
--- /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..29f3779
--- /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..e6c15a8
--- /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..b46e60e
--- /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..820f3a8
--- /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..aff2845
--- /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..6bd3d48
--- /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..ccfef18
--- /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..14652ef
--- /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..842dfd3
--- /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..036cb13
--- /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..dcf61af
--- /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..16ce334
--- /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..0b83548
--- /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..67abd61
--- /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..007b3ce
--- /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..6584e28
--- /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..0975acf
--- /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..8622345
--- /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..b142237
--- /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_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..4f2e24c
--- /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..6b74a0f
--- /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..444078d
--- /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..e6099b5
--- /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..bd7bf92
--- /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..63e108f
--- /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..f578d88
--- /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..904c2b6
--- /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..9a36b17
--- /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..41f76bb
--- /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..95f639a
--- /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..8164bde
--- /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..0663290
--- /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..c9efc4b
--- /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..398eb39
--- /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..18d9150
--- /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..7077089
--- /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..24305f1
--- /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..f65aa13
--- /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..00af328
--- /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..7fef5f5
--- /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..b5085b1
--- /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_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..5d061c9 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 c16c294..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 c16c294..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 91f8cd7..74e9270 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..9b3f875 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 f789bf0..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/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/ColorUtils.java b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
index 84d330b..597fb6e 100644
--- a/v7/palette/src/android/support/v7/graphics/ColorUtils.java
+++ b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
@@ -123,25 +123,15 @@
         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);
+    static int getTextColorForBackground(int backgroundColor, int textColor, float minContrastRatio) {
+        final int minAlpha = ColorUtils
+                .findMinimumAlpha(textColor, backgroundColor, minContrastRatio);
 
-        if (whiteMinAlpha >= 0) {
-            return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
+        if (minAlpha >= 0) {
+            return ColorUtils.modifyAlpha(textColor, minAlpha);
         }
 
-        // 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!
+        // Didn't find an opacity which provided enough contrast
         return -1;
     }
 
diff --git a/v7/palette/src/android/support/v7/graphics/Palette.java b/v7/palette/src/android/support/v7/graphics/Palette.java
index 0831b75..84543d5 100644
--- a/v7/palette/src/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/android/support/v7/graphics/Palette.java
@@ -186,6 +186,18 @@
                 }, 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) {
+        if (swatches == null) {
+            return null;
+        }
+        return new Palette(swatches);
+    }
+
     private Palette(List<Swatch> swatches) {
         mSwatches = swatches;
         mHighestPopulation = findMaxPopulation();
@@ -540,11 +552,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;
         }
 
@@ -605,10 +617,37 @@
 
         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 lightBody = ColorUtils.getTextColorForBackground(
+                        mRgb, Color.WHITE,  MIN_CONTRAST_BODY_TEXT);
+                final int lightTitle = ColorUtils.getTextColorForBackground(
+                        mRgb, Color.WHITE, MIN_CONTRAST_TITLE_TEXT);
+
+                if (lightBody != -1 && lightTitle != -1) {
+                    // If we found valid light values, use them and return
+                    mBodyTextColor = lightBody;
+                    mTitleTextColor = lightTitle;
+                    mGeneratedTextColors = true;
+                    return;
+                }
+
+                final int darkBody = ColorUtils.getTextColorForBackground(
+                        mRgb, Color.BLACK, MIN_CONTRAST_BODY_TEXT);
+                final int darkTitle = ColorUtils.getTextColorForBackground(
+                        mRgb, Color.BLACK, MIN_CONTRAST_TITLE_TEXT);
+
+                if (darkBody != -1 && darkBody != -1) {
+                    // If we found valid dark values, use them and return
+                    mBodyTextColor = darkBody;
+                    mTitleTextColor = darkTitle;
+                    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 = lightBody != -1 ? lightBody : darkBody;
+                mTitleTextColor = lightTitle != -1 ? lightTitle : darkTitle;
                 mGeneratedTextColors = true;
             }
         }
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index 40a58ad..4d0b564 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/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..d6e409d 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
@@ -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;
             }
@@ -207,6 +207,9 @@
         }
         mCallback.attachViewToParent(child, offset, layoutParams);
         mBucket.insert(offset, hidden);
+        if (hidden) {
+            mHiddenViews.add(child);
+        }
         if (DEBUG) {
             Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
                     "h:" + hidden + ", " + this);
@@ -311,7 +314,7 @@
 
     @Override
     public String toString() {
-        return mBucket.toString();
+        return mBucket.toString() + ", hidden list:" + mHiddenViews.size();
     }
 
     /**
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 2a27d65..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..8d6dbf7 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -217,6 +217,7 @@
         }
         SavedState state = new SavedState();
         if (getChildCount() > 0) {
+            ensureLayoutState();
             boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
             state.mAnchorLayoutFromEnd = didLayoutFromEnd;
             if (didLayoutFromEnd) {
@@ -460,8 +461,10 @@
         int extraForStart;
         int extraForEnd;
         final int extra = getExtraLayoutSpace(state);
-        boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition;
-        if (before == mShouldReverseLayout) {
+        // default extra space to the tail of the list.
+        boolean before = state.hasTargetScrollPosition() &&
+                state.getTargetScrollPosition() < mAnchorInfo.mPosition;
+        if (before == mAnchorInfo.mLayoutFromEnd) {
             extraForEnd = extra;
             extraForStart = 0;
         } else {
@@ -599,7 +602,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) {
@@ -990,27 +993,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);
     }
 
     /**
@@ -1428,6 +1437,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 +1514,7 @@
     }
 
     private View findReferenceChild(int start, int end, int itemCount) {
+        ensureLayoutState();
         View invalidMatch = null;
         View outOfBoundsMatch = null;
         final int boundsStart = mOrientationHelper.getStartAfterPadding();
@@ -1496,7 +1542,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 +1560,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 +1577,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 +1600,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 +1617,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 +1636,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 +1659,7 @@
         if (layoutDir == LayoutState.INVALID_LAYOUT) {
             return null;
         }
+        ensureLayoutState();
         final View referenceChild;
         if (layoutDir == LayoutState.LAYOUT_START) {
             referenceChild = findReferenceChildClosestToStart(state);
@@ -1821,7 +1877,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 +1894,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;
@@ -1945,8 +2002,8 @@
          */
         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 (!lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
+                    && lp.getViewLayoutPosition() < state.getItemCount()) {
                 assignFromView(child);
                 return true;
             }
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 149a1df..e5d4a29 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -31,6 +31,7 @@
 import android.support.v4.view.MotionEventCompat;
 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;
@@ -82,6 +83,46 @@
  *     <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 {
     private static final String TAG = "RecyclerView";
@@ -106,6 +147,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 +190,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,7 +276,7 @@
     private int mInitialTouchY;
     private int mLastTouchX;
     private int mLastTouchY;
-    private final int mTouchSlop;
+    private int mTouchSlop;
     private final int mMinFlingVelocity;
     private final int mMaxFlingVelocity;
 
@@ -241,6 +293,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() {
@@ -359,11 +416,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);
             }
         });
@@ -469,6 +551,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 +592,7 @@
      */
     public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
         setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
-        mDataSetHasChangedAfterLayout = true;
+        setDataSetChangedAfterLayout();
         requestLayout();
     }
     /**
@@ -514,6 +622,7 @@
             boolean removeAndRecycleViews) {
         if (mAdapter != null) {
             mAdapter.unregisterAdapterDataObserver(mObserver);
+            mAdapter.onDetachedFromRecyclerView(this);
         }
         if (!compatibleWithPrevious || removeAndRecycleViews) {
             // end all running animations
@@ -526,14 +635,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 +748,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 +767,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 +782,7 @@
             }
         }
         resumeRequestLayout(false);
+        return removed;
     }
 
     /**
@@ -750,7 +869,9 @@
         if (mScrollListener != null) {
             mScrollListener.onScrollStateChanged(this, state);
         }
-        mLayout.onScrollStateChanged(state);
+        if (mLayout != null) {
+            mLayout.onScrollStateChanged(state);
+        }
     }
 
     /**
@@ -841,6 +962,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 +987,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 +1004,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 +1024,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 +1079,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;
     }
 
     /**
@@ -1112,6 +1242,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 +1265,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 +1308,9 @@
      */
     private void stopScrollersInternal() {
         mViewFlinger.stop();
-        mLayout.stopSmoothScroller();
+        if (mLayout != null) {
+            mLayout.stopSmoothScroller();
+        }
     }
 
     /**
@@ -1303,7 +1457,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);
@@ -1531,7 +1685,6 @@
                         startScroll = true;
                     }
                     if (startScroll) {
-                        getParent().requestDisallowInterceptTouchEvent(true);
                         setScrollState(SCROLL_STATE_DRAGGING);
                     }
                 }
@@ -1606,15 +1759,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;
@@ -1690,11 +1844,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 +2012,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 +2028,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 +2152,7 @@
                     mState.mPreLayoutHolderMap.removeAt(i);
 
                     View disappearingItemView = disappearingItem.holder.itemView;
-                    removeDetachedView(disappearingItemView, false);
                     mRecycler.unscrapView(disappearingItem.holder);
-
                     animateDisappearance(disappearingItem);
                 }
             }
@@ -2015,7 +2213,7 @@
             }
         }
         resumeRequestLayout(false);
-        mLayout.removeAndRecycleScrapInt(mRecycler, !mState.mRunPredictiveAnimations);
+        mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
         mState.mRunSimpleAnimations = false;
@@ -2026,6 +2224,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 +2353,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 +2385,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) {
@@ -2443,6 +2703,21 @@
         }
     }
 
+    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 +2773,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 +2822,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 +2886,7 @@
                     if (holder.mPosition == position) {
                         return holder;
                     }
-                } else if (holder.getPosition() == position) {
+                } else if (holder.getLayoutPosition() == position) {
                     return holder;
                 }
             }
@@ -2556,10 +2901,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 +3069,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 +3097,6 @@
                     mRunningLayoutOrScroll = false;
                     resumeRequestLayout(false);
                 }
-                final boolean fullyConsumedScroll = dx == hresult && dy == vresult;
                 if (!mItemDecorations.isEmpty()) {
                     invalidate();
                 }
@@ -2784,18 +3127,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 +3170,7 @@
             if (mEatRunOnAnimationRequest) {
                 mReSchedulePostAnimationCallback = true;
             } else {
+                removeCallbacks(this);
                 ViewCompat.postOnAnimation(RecyclerView.this, this);
             }
         }
@@ -2894,6 +3241,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 +3258,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 +3467,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);
             }
         }
 
@@ -3432,8 +3783,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 +3793,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 +3816,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,30 +3857,35 @@
                                 + (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()) {
+            //noinspection unchecked
+            final boolean forceRecycle = mAdapter != null
+                    && holder.doesTransientStatePreventRecycling()
+                    && mAdapter.onFailedToRecycleView(holder);
+            if (forceRecycle || holder.isRecyclable()) {
                 boolean cached = false;
                 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);
                 }
             } else if (DEBUG) {
                 Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
@@ -3538,6 +3896,12 @@
             mState.onViewRecycled(holder);
         }
 
+        void addViewHolderToRecycledViewPool(ViewHolder holder) {
+            ViewCompat.setAccessibilityDelegate(holder.itemView, null);
+            getRecycledViewPool().putRecycledView(holder);
+            dispatchViewRecycled(holder);
+        }
+
         /**
          * Used as a fast path for unscrapping and recycling a view during a bulk operation.
          * The caller must call {@link #clearScrap()} when it's done to update the recycler's
@@ -3614,7 +3978,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 +4015,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 +4042,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 +4099,7 @@
                         }
                         return holder;
                     } else if (!dryRun) {
-                        tryToRecycleCachedViewAt(i);
+                        recycleCachedViewAt(i);
                     }
                 }
             }
@@ -3794,7 +4158,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 +4180,16 @@
             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");
-                        }
+                        recycleCachedViewAt(i);
                     }
                 }
             }
@@ -3873,7 +4225,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 +4234,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 +4254,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 +4366,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.
@@ -4047,7 +4401,8 @@
             }
             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);
         }
 
         /**
@@ -4128,6 +4483,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 +4589,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 +5305,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 +5331,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 +5341,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 +5351,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 +5779,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 +6159,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 +6195,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 +6428,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 +6602,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 +6775,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 +6872,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 +6888,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 +6949,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.
@@ -6660,6 +7054,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;
@@ -6712,11 +7121,77 @@
             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 been removed from the RecyclerView.
+         */
+        public final int getAdapterPosition() {
+            final ViewParent parent = itemView.getParent();
+            if (!(parent instanceof RecyclerView)) {
+                return -1;
+            }
+            final RecyclerView rv = (RecyclerView) parent;
+            return rv.getAdapterPositionFor(this);
+        }
+
+        /**
          * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
          * to perform animations.
          * <p>
@@ -6764,6 +7239,10 @@
             mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
         }
 
+        void clearTmpDetachFlag() {
+            mFlags = mFlags & ~FLAG_TMP_DETACHED;
+        }
+
         void stopIgnoring() {
             mFlags = mFlags & ~FLAG_IGNORE;
         }
@@ -6792,6 +7271,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;
+        }
+
         void setFlags(int flags, int mask) {
             mFlags = (mFlags & ~mask) | (flags & mask);
         }
@@ -6823,7 +7314,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 +7363,30 @@
             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_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
+            return RecyclerView.NO_POSITION;
+        }
+        return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
     }
 
     /**
@@ -6949,13 +7467,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 +7660,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 +7740,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);
 
@@ -7679,14 +8216,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 +8232,7 @@
         @Override
         public void onMoveFinished(ViewHolder item) {
             item.setIsRecyclable(true);
-            if (item.isRecyclable()) {
+            if (!item.shouldBeKeptAsChild()) {
                 removeAnimatingView(item.itemView);
             }
         }
@@ -7736,7 +8274,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 +8311,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 +8396,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..724fac8 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/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..e96a782 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 {
@@ -159,6 +191,11 @@
     }
     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() {
@@ -182,6 +219,20 @@
             };
             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.getAdapterPosition(), RecyclerView.NO_POSITION);
+                    }
+                }
+            });
+        }
         mAdapterHelper = recyclerView.mAdapterHelper;
         runTestOnUiThread(new Runnable() {
             @Override
@@ -195,27 +246,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 {
@@ -249,7 +308,7 @@
 
     class TestViewHolder extends RecyclerView.ViewHolder {
 
-        Item mBindedItem;
+        Item mBoundItem;
 
         public TestViewHolder(View itemView) {
             super(itemView);
@@ -258,7 +317,7 @@
 
         @Override
         public String toString() {
-            return super.toString() + " item:" + mBindedItem;
+            return super.toString() + " item:" + mBoundItem;
         }
     }
 
@@ -303,7 +362,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 +466,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 +480,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()));
@@ -428,7 +513,7 @@
         public void onBindViewHolder(TestViewHolder holder, int position) {
             final Item item = mItems.get(position);
             ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
-            holder.mBindedItem = item;
+            holder.mBoundItem = item;
         }
 
         public void deleteAndNotify(final int start, final int count) throws Throwable {
@@ -525,6 +610,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 +623,9 @@
             }
         }
 
+        /**
+         * Uses notifyDataSetChanged
+         */
         public void moveItem(final int from, final int to, final boolean notifyChange)
                 throws Throwable {
             runTestOnUiThread(new Runnable() {
@@ -551,6 +642,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 +712,10 @@
         }
     }
 
+    public boolean isMainThread() {
+        return Looper.myLooper() == Looper.getMainLooper();
+    }
+
     @Override
     public void runTestOnUiThread(Runnable r) throws Throwable {
         if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -626,4 +744,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..fed7a1a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -109,7 +109,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,7 +125,7 @@
         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,
@@ -167,7 +167,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 +232,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 +329,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 +1059,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));
                     }
                 }
             });
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..2e430e5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -28,6 +28,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,6 +39,7 @@
 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;
@@ -52,6 +54,479 @@
         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 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 +890,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 +903,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 +922,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 +1012,7 @@
                     }
                     assertEquals(state.toString(),
                             expectedOnMeasureStateCount.get(), state.getItemCount());
-                } catch(Throwable t) {
+                } catch (Throwable t) {
                     postExceptionToInstrumentation(t);
                 }
                 super.onMeasure(recycler, state, widthSpec, heightSpec);
@@ -524,7 +1058,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 +1071,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 +1221,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 +1255,7 @@
         recyclerView.setItemViewCacheSize(5);
         recyclerView.setLayoutManager(testLayoutManager);
         testLayoutManager.expectLayouts(1);
-        setRecyclerView(recyclerView);
+        setRecyclerView(recyclerView, true, false);
         testLayoutManager.waitForLayout(2);
         checkForMainThreadException();
 
@@ -735,6 +1267,7 @@
         checkForMainThreadException();
 
         // invalidate w/o an item decorator
+
         invalidateDecorOffsets(recyclerView);
         testLayoutManager.expectLayouts(1);
         invalidateDecorOffsets(recyclerView);
@@ -788,7 +1321,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 +1363,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 +1397,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 +1409,19 @@
 
         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 +1747,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 +1817,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 +1869,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 +2006,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 +2085,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 +2210,33 @@
                 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();
+    }
+
     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 +2264,8 @@
         }
     }
 
+    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 {