Merge "Add vendor display name and ID to artifact source properties" into nyc-mr1-dev
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 61d036b..4907e2a 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -12,6 +12,14 @@
 
 jar {
     from sourceSets.main.output
+    // Strip out typedef classes. For Android libraries, this is done
+    // automatically by the Gradle plugin, but the Annotation library is a
+    // plain jar, built by the regular Gradle java plugin. The typedefs
+    // themselves have been manually extracted into the
+    // external-annotations directory, and those are packaged separately
+    // below by the annotationsZip task.
+    exclude('android/support/annotation/ProductionVisibility.class')
+    exclude('android/support/annotation/DimensionUnit.class')
 }
 
 uploadArchives {
@@ -72,9 +80,15 @@
     from               javadoc.destinationDir
 }
 
-// add javadoc/source jar tasks as artifacts
+task annotationsZip(type: Zip) {
+    classifier         'annotations'
+    from               'external-annotations'
+}
+
+// add javadoc/source/annotations jar tasks as artifacts
 artifacts {
     archives jar
     archives sourcesJar
     archives javadocJar
+    archives annotationsZip
 }
diff --git a/annotations/external-annotations/android/support/annotation/annotations.xml b/annotations/external-annotations/android/support/annotation/annotations.xml
new file mode 100644
index 0000000..23e9a01
--- /dev/null
+++ b/annotations/external-annotations/android/support/annotation/annotations.xml
@@ -0,0 +1,12 @@
+<root>
+    <item name="android.support.annotation.VisibleForTesting int otherwise()">
+        <annotation name="android.support.annotation.IntDef">
+            <val name="value" val="{android.support.annotation.VisibleForTesting.PRIVATE, android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE, android.support.annotation.VisibleForTesting.PROTECTED, android.support.annotation.VisibleForTesting.NONE}" />
+        </annotation>
+    </item>
+    <item name="android.support.annotation.Dimension int unit()">
+        <annotation name="android.support.annotation.IntDef">
+            <val name="value" val="{android.support.annotation.Dimension.DP, android.support.annotation.Dimension.PX, android.support.annotation.Dimension.SP}" />
+        </annotation>
+    </item>
+</root>
diff --git a/annotations/src/android/support/annotation/Dimension.java b/annotations/src/android/support/annotation/Dimension.java
index cb4fb5e..b8c5590 100644
--- a/annotations/src/android/support/annotation/Dimension.java
+++ b/annotations/src/android/support/annotation/Dimension.java
@@ -18,7 +18,6 @@
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
@@ -36,14 +35,10 @@
 @Retention(CLASS)
 @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
 public @interface Dimension {
-    @Unit
+    @DimensionUnit
     int unit() default PX;
 
     int DP = 0;
     int PX = 1;
     int SP = 2;
-
-    @IntDef({PX, DP, SP})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface Unit {}
 }
diff --git a/annotations/src/android/support/annotation/DimensionUnit.java b/annotations/src/android/support/annotation/DimensionUnit.java
new file mode 100644
index 0000000..d4d6398
--- /dev/null
+++ b/annotations/src/android/support/annotation/DimensionUnit.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.annotation;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Typedef for the {@link Dimension#unit} attribute.
+ *
+ * @hide
+ */
+@IntDef({Dimension.PX,
+         Dimension.DP,
+         Dimension.SP}
+// Important: If updating these constants, also update
+// ../../../../external-annotations/android/support/annotation/annotations.xml
+)
+@Retention(SOURCE)
+@interface DimensionUnit {
+}
diff --git a/annotations/src/android/support/annotation/ProductionVisibility.java b/annotations/src/android/support/annotation/ProductionVisibility.java
new file mode 100644
index 0000000..6bd978e
--- /dev/null
+++ b/annotations/src/android/support/annotation/ProductionVisibility.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.annotation;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Typedef for the {@link VisibleForTesting#otherwise} attribute.
+ *
+ * @hide
+ */
+@IntDef({VisibleForTesting.PRIVATE,
+         VisibleForTesting.PACKAGE_PRIVATE,
+         VisibleForTesting.PROTECTED,
+         VisibleForTesting.NONE}
+// Important: If updating these constants, also update
+// ../../../../external-annotations/android/support/annotation/annotations.xml
+)
+@Retention(SOURCE)
+@interface ProductionVisibility {
+}
diff --git a/annotations/src/android/support/annotation/VisibleForTesting.java b/annotations/src/android/support/annotation/VisibleForTesting.java
index ddce1d5..16d915a 100644
--- a/annotations/src/android/support/annotation/VisibleForTesting.java
+++ b/annotations/src/android/support/annotation/VisibleForTesting.java
@@ -18,7 +18,6 @@
 import java.lang.annotation.Retention;
 
 import static java.lang.annotation.RetentionPolicy.CLASS;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 /**
  * Denotes that the class, method or field has its visibility relaxed, so that it is more widely
@@ -66,9 +65,4 @@
      * This is equivalent to {@code @RestrictTo.Scope.TESTS}.
      */
     int NONE = 5;
-
-    @IntDef({PRIVATE, PACKAGE_PRIVATE, PROTECTED, NONE})
-    @Retention(SOURCE)
-    @interface ProductionVisibility {
-    }
 }
diff --git a/api/current.txt b/api/current.txt
index e4cdafc..806a368 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -156,7 +156,7 @@
 
   public final class CustomTabsIntent {
     method public static int getMaxToolbarItems();
-    method public void launchUrl(android.app.Activity, android.net.Uri);
+    method public void launchUrl(android.content.Context, android.net.Uri);
     method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent);
     method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
     field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
@@ -3729,7 +3729,6 @@
     method public static void setEnterSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
     method public static void setExitSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
     method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String);
-    method public static void startActivity(android.app.Activity, android.content.Intent, android.os.Bundle);
     method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle);
     method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public static void startPostponedEnterTransition(android.app.Activity);
@@ -4432,6 +4431,7 @@
     method public android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Builder);
     method public java.util.List<android.support.v4.app.NotificationCompat.Action> getActions();
     method public android.graphics.Bitmap getBackground();
+    method public java.lang.String getBridgeTag();
     method public int getContentAction();
     method public int getContentIcon();
     method public int getContentIconGravity();
@@ -4450,6 +4450,7 @@
     method public java.util.List<android.app.Notification> getPages();
     method public boolean getStartScrollBottom();
     method public android.support.v4.app.NotificationCompat.WearableExtender setBackground(android.graphics.Bitmap);
+    method public android.support.v4.app.NotificationCompat.WearableExtender setBridgeTag(java.lang.String);
     method public android.support.v4.app.NotificationCompat.WearableExtender setContentAction(int);
     method public android.support.v4.app.NotificationCompat.WearableExtender setContentIcon(int);
     method public android.support.v4.app.NotificationCompat.WearableExtender setContentIconGravity(int);
@@ -4683,6 +4684,7 @@
     method public static boolean isDeviceProtectedStorage(android.content.Context);
     method public static boolean startActivities(android.content.Context, android.content.Intent[]);
     method public static boolean startActivities(android.content.Context, android.content.Intent[], android.os.Bundle);
+    method public static void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
   }
 
   public class CursorLoader extends android.support.v4.content.AsyncTaskLoader {
diff --git a/build.gradle b/build.gradle
index a3f46a3..9d02725 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@
         maven { url "../../prebuilts/maven_repo/android" }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.0-rc1'
+        classpath 'com.android.tools.build:gradle:2.2.0'
     }
 }
 
diff --git a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java b/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java
deleted file mode 100644
index afac122..0000000
--- a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorBase.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.os;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-class ParcelableCompatCreatorBase<T> implements Parcelable.Creator<T> {
-    final ParcelableCompatCreatorCallbacks<T> mCallbacks;
-
-    public ParcelableCompatCreatorBase(ParcelableCompatCreatorCallbacks<T> callbacks) {
-        mCallbacks = callbacks;
-    }
-
-    @Override
-    public T createFromParcel(Parcel source) {
-        return mCallbacks.createFromParcel(source, null);
-    }
-
-    @Override
-    public T[] newArray(int size) {
-        return mCallbacks.newArray(size);
-    }
-}
diff --git a/compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorCallbacks.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
similarity index 100%
rename from compat/gingerbread/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
rename to compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorCallbacks.java
diff --git a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java
deleted file mode 100644
index 61a5924..0000000
--- a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.os;
-
-import android.os.Parcel;
-
-class ParcelableCompatCreatorHoneycombMR2<T> extends ParcelableCompatCreatorBase<T> {
-
-    public ParcelableCompatCreatorHoneycombMR2(ParcelableCompatCreatorCallbacks<T> callbacks) {
-        super(callbacks);
-    }
-
-    public T createFromParcel(Parcel in, ClassLoader loader) {
-        return mCallbacks.createFromParcel(in, loader);
-    }
-}
diff --git a/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java
new file mode 100644
index 0000000..08acb55
--- /dev/null
+++ b/compat/honeycomb_mr2/android/support/v4/os/ParcelableCompatHoneycombMR2.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+class ParcelableCompatCreatorHoneycombMR2Stub {
+    static <T> Parcelable.Creator<T> instantiate(ParcelableCompatCreatorCallbacks<T> callbacks) {
+        return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks);
+    }
+}
+
+class ParcelableCompatCreatorHoneycombMR2<T> implements Parcelable.ClassLoaderCreator<T> {
+    private final ParcelableCompatCreatorCallbacks<T> mCallbacks;
+
+    public ParcelableCompatCreatorHoneycombMR2(ParcelableCompatCreatorCallbacks<T> callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    public T createFromParcel(Parcel in) {
+        return mCallbacks.createFromParcel(in, null);
+    }
+
+    public T createFromParcel(Parcel in, ClassLoader loader) {
+        return mCallbacks.createFromParcel(in, loader);
+    }
+
+    public T[] newArray(int size) {
+        return mCallbacks.newArray(size);
+    }
+}
diff --git a/compat/java/android/support/v4/app/ActivityCompat.java b/compat/java/android/support/v4/app/ActivityCompat.java
index b97d101..c30e49d 100644
--- a/compat/java/android/support/v4/app/ActivityCompat.java
+++ b/compat/java/android/support/v4/app/ActivityCompat.java
@@ -120,31 +120,6 @@
     }
 
     /**
-     * Start an activity with additional launch information, if able.
-     *
-     * <p>In Android 4.1+ additional options were introduced to allow for more
-     * control on activity launch animations. Applications can use this method
-     * along with {@link ActivityOptionsCompat} to use these animations when
-     * available. When run on versions of the platform where this feature does
-     * not exist the activity will be launched normally.</p>
-     *
-     * @param activity Context to launch activity from.
-     * @param intent The description of the activity to start.
-     * @param options Additional options for how the Activity should be started.
-     *                May be null if there are no options. See
-     *                {@link ActivityOptionsCompat} for how to build the Bundle
-     *                supplied here; there are no supported definitions for
-     *                building it manually.
-     */
-    public static void startActivity(Activity activity, Intent intent, @Nullable Bundle options) {
-        if (Build.VERSION.SDK_INT >= 16) {
-            ActivityCompatJB.startActivity(activity, intent, options);
-        } else {
-            activity.startActivity(intent);
-        }
-    }
-
-    /**
      * Start new activity with options, if able, for which you would like a
      * result when it finished.
      *
diff --git a/compat/java/android/support/v4/app/ActivityOptionsCompat.java b/compat/java/android/support/v4/app/ActivityOptionsCompat.java
index 692b202..68fc441 100644
--- a/compat/java/android/support/v4/app/ActivityOptionsCompat.java
+++ b/compat/java/android/support/v4/app/ActivityOptionsCompat.java
@@ -442,7 +442,7 @@
 
     /**
      * Returns the created options as a Bundle, which can be passed to
-     * {@link ActivityCompat#startActivity(android.app.Activity, android.content.Intent, android.os.Bundle)}.
+     * {@link android.support.v4.content.ContextCompat#startActivity(Context, android.content.Intent, Bundle)}.
      * Note that the returned Bundle is still owned by the ActivityOptions
      * object; you must not modify it, but can supply it to the startActivity
      * methods that take an options Bundle.
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index fdf5a87..e2976c2 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -3011,6 +3011,7 @@
         private static final String KEY_GRAVITY = "gravity";
         private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
         private static final String KEY_DISMISSAL_ID = "dismissalId";
+        private static final String KEY_BRIDGE_TAG = "bridgeTag";
 
         // Flags bitwise-ored to mFlags
         private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
@@ -3040,6 +3041,7 @@
         private int mGravity = DEFAULT_GRAVITY;
         private int mHintScreenTimeout;
         private String mDismissalId;
+        private String mBridgeTag;
 
         /**
          * Create a {@link NotificationCompat.WearableExtender} with default
@@ -3080,6 +3082,7 @@
                 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
                 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
                 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
+                mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG);
             }
         }
 
@@ -3135,6 +3138,9 @@
             if (mDismissalId != null) {
                 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
             }
+            if (mBridgeTag != null) {
+                wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag);
+            }
 
             builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
             return builder;
@@ -3156,6 +3162,7 @@
             that.mGravity = this.mGravity;
             that.mHintScreenTimeout = this.mHintScreenTimeout;
             that.mDismissalId = this.mDismissalId;
+            that.mBridgeTag = this.mBridgeTag;
             return that;
         }
 
@@ -3644,12 +3651,11 @@
         }
 
         /**
-         * When you post a notification, if you set the dismissal id field, then when that
-         * notification is canceled, notifications on other wearables and the paired Android phone
-         * having that same dismissal id will also be canceled.  Note that this only works if you
-         * have notification bridge mode set to NO_BRIDGING in your Wear app manifest.  See
+         * Sets the dismissal id for this notification. If a notification is posted with a
+         * dismissal id, then when that notification is canceled, notifications on other wearables
+         * and the paired Android phone having that same dismissal id will also be canceled. See
          * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
-         * Notifications</a> for more information on how to use the bridge mode feature.
+         * Notifications</a> for more information.
          * @param dismissalId the dismissal id of the notification.
          * @return this object for method chaining
          */
@@ -3666,6 +3672,27 @@
             return mDismissalId;
         }
 
+        /**
+         * Sets a bridge tag for this notification. A bridge tag can be set for notifications
+         * posted from a phone to provide finer-grained control on what notifications are bridged
+         * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable
+         * Features to Notifications</a> for more information.
+         * @param bridgeTag the bridge tag of the notification.
+         * @return this object for method chaining
+         */
+        public WearableExtender setBridgeTag(String bridgeTag) {
+            mBridgeTag = bridgeTag;
+            return this;
+        }
+
+        /**
+         * Returns the bridge tag of the notification.
+         * @return the bridge tag or null if not present.
+         */
+        public String getBridgeTag() {
+            return mBridgeTag;
+        }
+
         private void setFlag(int mask, boolean value) {
             if (value) {
                 mFlags |= mask;
diff --git a/compat/java/android/support/v4/content/ContextCompat.java b/compat/java/android/support/v4/content/ContextCompat.java
index 05486c2..d51d6cb 100644
--- a/compat/java/android/support/v4/content/ContextCompat.java
+++ b/compat/java/android/support/v4/content/ContextCompat.java
@@ -29,6 +29,8 @@
 import android.support.annotation.ColorRes;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.os.BuildCompat;
 import android.support.v4.os.EnvironmentCompat;
 import android.util.Log;
@@ -128,6 +130,31 @@
     }
 
     /**
+     * Start an activity with additional launch information, if able.
+     *
+     * <p>In Android 4.1+ additional options were introduced to allow for more
+     * control on activity launch animations. Applications can use this method
+     * along with {@link ActivityOptionsCompat} to use these animations when
+     * available. When run on versions of the platform where this feature does
+     * not exist the activity will be launched normally.</p>
+     *
+     * @param context Context to launch activity from.
+     * @param intent The description of the activity to start.
+     * @param options Additional options for how the Activity should be started.
+     *                May be null if there are no options. See
+     *                {@link ActivityOptionsCompat} for how to build the Bundle
+     *                supplied here; there are no supported definitions for
+     *                building it manually.
+     */
+    public static void startActivity(Context context, Intent intent, @Nullable Bundle options) {
+        if (Build.VERSION.SDK_INT >= 16) {
+            ContextCompatJellybean.startActivity(context, intent, options);
+        } else {
+            context.startActivity(intent);
+        }
+    }
+
+    /**
      * Returns the absolute path to the directory on the filesystem where all
      * private files belonging to this app are stored. Apps should not use this
      * path directly; they should instead use {@link Context#getFilesDir()},
diff --git a/compat/java/android/support/v4/os/ParcelableCompat.java b/compat/java/android/support/v4/os/ParcelableCompat.java
index 6c8820ca..10c03b5 100644
--- a/compat/java/android/support/v4/os/ParcelableCompat.java
+++ b/compat/java/android/support/v4/os/ParcelableCompat.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.os;
 
+import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
@@ -23,6 +24,7 @@
  * introduced after API level 4 in a backwards compatible fashion.
  */
 public final class ParcelableCompat {
+
     /**
      * Factory method for {@link Parcelable.Creator}.
      *
@@ -32,9 +34,27 @@
     public static <T> Parcelable.Creator<T> newCreator(
             ParcelableCompatCreatorCallbacks<T> callbacks) {
         if (android.os.Build.VERSION.SDK_INT >= 13) {
-            return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks);
+            return ParcelableCompatCreatorHoneycombMR2Stub.instantiate(callbacks);
         }
-        return new ParcelableCompatCreatorBase<T>(callbacks);
+        return new CompatCreator<T>(callbacks);
+    }
+
+    static class CompatCreator<T> implements Parcelable.Creator<T> {
+        final ParcelableCompatCreatorCallbacks<T> mCallbacks;
+
+        public CompatCreator(ParcelableCompatCreatorCallbacks<T> callbacks) {
+            mCallbacks = callbacks;
+        }
+
+        @Override
+        public T createFromParcel(Parcel source) {
+            return mCallbacks.createFromParcel(source, null);
+        }
+
+        @Override
+        public T[] newArray(int size) {
+            return mCallbacks.newArray(size);
+        }
     }
 
     private ParcelableCompat() {}
diff --git a/compat/jellybean/android/support/v4/app/ActivityCompatJB.java b/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
index 77fe7fb..13e6e4e 100644
--- a/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
+++ b/compat/jellybean/android/support/v4/app/ActivityCompatJB.java
@@ -17,16 +17,11 @@
 package android.support.v4.app;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
 
 class ActivityCompatJB {
-    public static void startActivity(Context context, Intent intent, Bundle options) {
-        context.startActivity(intent, options);
-    }
-
     public static void startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
         activity.startActivityForResult(intent, requestCode, options);
     }
diff --git a/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java b/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
index c353b96..5e9f910 100644
--- a/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
+++ b/compat/jellybean/android/support/v4/content/ContextCompatJellybean.java
@@ -26,4 +26,8 @@
         context.startActivities(intents, options);
     }
 
+    public static void startActivity(Context context, Intent intent, Bundle options) {
+        context.startActivity(intent, options);
+    }
+
 }
diff --git a/customtabs/src/android/support/customtabs/CustomTabsIntent.java b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
index 75214ee..f2de314 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsIntent.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
@@ -28,9 +28,9 @@
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.app.BundleCompat;
+import android.support.v4.content.ContextCompat;
 import android.view.View;
 import android.widget.RemoteViews;
 
@@ -254,12 +254,12 @@
 
     /**
      * Convenience method to launch a Custom Tabs Activity.
-     * @param context The source Activity.
+     * @param context The source Context.
      * @param url The URL to load in the Custom Tab.
      */
-    public void launchUrl(Activity context, Uri url) {
+    public void launchUrl(Context context, Uri url) {
         intent.setData(url);
-        ActivityCompat.startActivity(context, intent, startAnimationBundle);
+        ContextCompat.startActivity(context, intent, startAnimationBundle);
     }
 
     private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java b/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
index 29f8a91f..4a6181d 100644
--- a/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
+++ b/samples/Support13Demos/src/com/example/android/supportv13/view/inputmethod/CommitContentSupport.java
@@ -29,18 +29,13 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.Gravity;
-import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.webkit.WebView;
 import android.widget.EditText;
-import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import static android.widget.LinearLayout.VERTICAL;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -120,7 +115,7 @@
                 mCurrentInputContentInfo.releasePermission();
             }
         } catch (Exception e) {
-            Log.e(TAG, "InputContentInfo#releasePermission() failed.", e);
+            Log.e(TAG, "InputContentInfoCompat#releasePermission() failed.", e);
         } finally {
             mCurrentInputContentInfo = null;
         }
@@ -151,7 +146,7 @@
             try {
                 inputContentInfo.requestPermission();
             } catch (Exception e) {
-                Log.e(TAG, "InputContentInfo#requestPermission() failed.", e);
+                Log.e(TAG, "InputContentInfoCompat#requestPermission() failed.", e);
                 return false;
             }
         }
diff --git a/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java b/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
index c1bffd42..79d7066 100644
--- a/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/EditorInfoCompat.java
@@ -23,6 +23,10 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 
+/**
+ * Helper for accessing features in {@link EditorInfo} introduced after API level 13 in a backwards
+ * compatible fashion.
+ */
 public final class EditorInfoCompat {
 
     private interface EditorInfoCompatImpl {
diff --git a/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java b/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
index b740502..07a76aa 100644
--- a/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/InputConnectionCompat.java
@@ -203,10 +203,8 @@
      * <a href="{@docRoot}training/secure-file-sharing/index.html">Sharing Files</a>.
      *
      * <p>Make sure that the content provider owning the Uri sets the
-     * {@link android.R.styleable#AndroidManifestProvider_grantUriPermissions
-     * grantUriPermissions} attribute in its manifest or included the
-     * {@link android.R.styleable#AndroidManifestGrantUriPermission
-     * &lt;grant-uri-permissions&gt;} tag.</p>
+     * {@link android.R.attr#grantUriPermissions grantUriPermissions} attribute in its manifest or
+     * included the {@code &lt;grant-uri-permissions&gt;} tag.</p>
      *
      * <p>Supported only on API &gt;= 25.</p>
      *
diff --git a/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java b/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
index 6f20594..43ad7ff 100644
--- a/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
+++ b/v13/java/android/support/v13/view/inputmethod/InputContentInfoCompat.java
@@ -22,6 +22,10 @@
 import android.support.annotation.Nullable;
 import android.support.v4.os.BuildCompat;
 
+/**
+ * Helper for accessing features in InputContentInfo introduced after API level 13 in a backwards
+ * compatible fashion.
+ */
 public final class InputContentInfoCompat {
 
     private interface InputContentInfoCompatImpl {
@@ -144,6 +148,19 @@
 
     private final InputContentInfoCompatImpl mImpl;
 
+    /**
+     * Constructs {@link InputContentInfoCompat}.
+     *
+     * @param contentUri content URI to be exported from the input method. This cannot be
+     *                   {@code null}.
+     * @param description a {@link ClipDescription} object that contains the metadata of
+     *                    {@code contentUri} such as MIME type(s). This object cannot be
+     *                    {@code null}. Also {@link ClipDescription#getLabel()} should be describing
+     *                    the content specified by {@code contentUri} for accessibility reasons.
+     * @param linkUri an optional {@code http} or {@code https} URI. The editor author may provide
+     *                a way to navigate the user to the specified web page if this is not
+     *                {@code null}.
+     */
     public InputContentInfoCompat(@NonNull Uri contentUri,
             @NonNull ClipDescription description, @Nullable Uri linkUri) {
         if (BuildCompat.isAtLeastNMR1()) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index 004f6f0..2cf897f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -492,14 +492,18 @@
             boolean canPause = keyEvent == null ||
                     keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
                     keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
-            if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
-                if (canPlay) {
-                    mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
-                    startPlayback(mPlaybackSpeed);
-                }
-            } else if (canPause) {
+            //            PLAY_PAUSE    PLAY      PAUSE
+            // playing    paused                  paused
+            // paused     playing       playing
+            // ff/rw      playing       playing   paused
+            if (canPause &&
+                (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
+                        mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
                 mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
                 pausePlayback();
+            } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+                mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+                startPlayback(mPlaybackSpeed);
             }
             updatePlaybackStatusAfterUserAction();
             handled = true;
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
index 3dcab93..5dbec44 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
@@ -348,4 +348,161 @@
             assertEquals(0, rewind.getIndex());
         }
     }
+
+    @Test
+    public void testMediaPauseButtonOnFF() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+        PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(fastForward);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPauseButtonOnPlay() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPauseButtonOnPause() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayButtonOnFF() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+        PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(fastForward);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayButtonOnPlay() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayButtonOnPause() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayPauseButtonOnFF() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+        PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_FAST_FORWARD);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(fastForward);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayPauseButtonOnPlay() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testMediaPlayPauseButtonOnPause() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        glue.onActionClicked(playPause);
+        glue.onActionClicked(playPause);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+        glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
 }
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index cd6f57e..9d8cf07 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.app;
 
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -39,8 +41,6 @@
 
 import java.util.List;
 
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
 /**
  * An extension of {@link android.support.v4.app.NotificationCompat} which supports
  * {@link android.support.v7.app.NotificationCompat.MediaStyle},
@@ -172,25 +172,17 @@
                     ? b.getColor()
                     : color;
         }
-        CharSequence senderText = bidiWrapIfNotSpanned(bidi, replyName);
+        CharSequence senderText = bidi.unicodeWrap(replyName);
         sb.append(senderText);
         sb.setSpan(makeFontColorSpan(color),
                 sb.length() - senderText.length(),
                 sb.length(),
                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
         CharSequence text = m.getText() == null ? "" : m.getText();
-        sb.append("  ").append(bidiWrapIfNotSpanned(bidi, text));
+        sb.append("  ").append(bidi.unicodeWrap(text));
         return sb;
     }
 
-    private static CharSequence bidiWrapIfNotSpanned(BidiFormatter bidi, CharSequence replyName) {
-        // Unfortunately bidiFormatter doesn't support CharSequences in support
-        if (replyName instanceof Spanned) {
-            return replyName;
-        }
-        return bidi.unicodeWrap(replyName.toString());
-    }
-
     private static TextAppearanceSpan makeFontColorSpan(int color) {
         return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
     }
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index b224b42..79df8f4 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -856,9 +856,11 @@
 
         switch (heightMode) {
             case MeasureSpec.AT_MOST:
-            case MeasureSpec.UNSPECIFIED:
                 height = Math.min(getPreferredHeight(), height);
                 break;
+            case MeasureSpec.UNSPECIFIED:
+                height = getPreferredHeight();
+                break;
         }
         heightMode = MeasureSpec.EXACTLY;
 
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index 19c62db..5040bba 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -22,8 +22,6 @@
 import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
-import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.widget.VectorEnabledTintResources;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -39,25 +37,38 @@
 @RestrictTo(GROUP_ID)
 public class TintContextWrapper extends ContextWrapper {
 
-    private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
+    private static final Object CACHE_LOCK = new Object();
+    private static ArrayList<WeakReference<TintContextWrapper>> sCache;
 
     public static Context wrap(@NonNull final Context context) {
         if (shouldWrap(context)) {
-            // First check our instance cache
-            for (int i = 0, count = sCache.size(); i < count; i++) {
-                final WeakReference<TintContextWrapper> ref = sCache.get(i);
-                final TintContextWrapper wrapper = ref != null ? ref.get() : null;
-                if (wrapper != null && wrapper.getBaseContext() == context) {
-                    return wrapper;
+            synchronized (CACHE_LOCK) {
+                if (sCache == null) {
+                    sCache = new ArrayList<>();
+                } else {
+                    // This is a convenient place to prune any dead reference entries
+                    for (int i = sCache.size() - 1; i >= 0; i--) {
+                        final WeakReference<TintContextWrapper> ref = sCache.get(i);
+                        if (ref == null || ref.get() == null) {
+                            sCache.remove(i);
+                        }
+                    }
+                    // Now check our instance cache
+                    for (int i = sCache.size() - 1; i >= 0; i--) {
+                        final WeakReference<TintContextWrapper> ref = sCache.get(i);
+                        final TintContextWrapper wrapper = ref != null ? ref.get() : null;
+                        if (wrapper != null && wrapper.getBaseContext() == context) {
+                            return wrapper;
+                        }
+                    }
                 }
+                // If we reach here then the cache didn't have a hit, so create a new instance
+                // and add it to the cache
+                final TintContextWrapper wrapper = new TintContextWrapper(context);
+                sCache.add(new WeakReference<>(wrapper));
+                return wrapper;
             }
-            // If we reach here then the cache didn't have a hit, so create a new instance
-            // and add it to the cache
-            final TintContextWrapper wrapper = new TintContextWrapper(context);
-            sCache.add(new WeakReference<>(wrapper));
-            return wrapper;
         }
-
         return context;
     }
 
@@ -69,14 +80,7 @@
             // If the Context is already a TintContextWrapper, no need to wrap again
             return false;
         }
-        if (AppCompatDelegate.isCompatVectorFromResourcesEnabled()
-                && Build.VERSION.SDK_INT > VectorEnabledTintResources.MAX_SDK_WHERE_REQUIRED) {
-            // If we're running on API 21+ and have the vector resources enabled, there's
-            // no need to wrap
-            return false;
-        }
-        // Else, we should wrap
-        return true;
+        return Build.VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
     }
 
     private final Resources mResources;
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
index b375b45..25f0b69 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
@@ -23,8 +23,8 @@
 import android.util.AttributeSet;
 
 /**
- * Used to group {@link android.preference.Preference} objects
- * and provide a disabled title above the group.
+ * Used to group {@link Preference} objects and provide a disabled title above
+ * the group.
  *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 88e0c41..33f4ede 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -4439,7 +4439,7 @@
         private int mDx;
         private int mDy;
 
-        private int[] mItemPrefetchArray;
+        int[] mItemPrefetchArray;
 
         /**
          * Schedule a prefetch immediately after the current traversal.
@@ -4456,6 +4456,24 @@
             }
         }
 
+        public boolean lastPrefetchIncludedPosition(int position) {
+            if (mItemPrefetchArray != null) {
+                for (int i = 0; i < mItemPrefetchArray.length; i++) {
+                    if (mItemPrefetchArray[i] == position) return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Called when prefetch indices are no longer valid for cache prioritization.
+         */
+        public void clearPrefetchPositions() {
+            if (mItemPrefetchArray != null) {
+                Arrays.fill(mItemPrefetchArray, -1);
+            }
+        }
+
         @Override
         public void run() {
             try {
@@ -4612,6 +4630,9 @@
 
                 if (scroller.isFinished() || !fullyConsumedAny) {
                     setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
+                    if (ALLOW_PREFETCHING) {
+                        mViewPrefetcher.clearPrefetchPositions();
+                    }
                 } else {
                     postOnAnimation();
                     if (ALLOW_PREFETCHING) {
@@ -5346,6 +5367,9 @@
                 recycleCachedViewAt(i);
             }
             mCachedViews.clear();
+            if (ALLOW_PREFETCHING) {
+                mViewPrefetcher.clearPrefetchPositions();
+            }
         }
 
         /**
@@ -5406,18 +5430,33 @@
                         holder);
             }
             if (forceRecycle || holder.isRecyclable()) {
-                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
-                        | ViewHolder.FLAG_UPDATE)) {
+                if (mViewCacheMax > 0
+                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
+                                | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
                     // Retire oldest cached view
                     int cachedViewSize = mCachedViews.size();
                     if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                         recycleCachedViewAt(0);
-                        cachedViewSize --;
+                        cachedViewSize--;
                     }
-                    if (cachedViewSize < mViewCacheMax) {
-                        mCachedViews.add(holder);
-                        cached = true;
+
+                    int targetCacheIndex = cachedViewSize;
+                    if (ALLOW_PREFETCHING
+                            && cachedViewSize > 0
+                            && !mViewPrefetcher.lastPrefetchIncludedPosition(holder.mPosition)) {
+                        // when adding the view, skip past most recently prefetched views
+                        int cacheIndex = cachedViewSize - 1;
+                        while (cacheIndex >= 0) {
+                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
+                            if (!mViewPrefetcher.lastPrefetchIncludedPosition(cachedPos)) {
+                                break;
+                            }
+                            cacheIndex--;
+                        }
+                        targetCacheIndex = cacheIndex + 1;
                     }
+                    mCachedViews.add(targetCacheIndex, holder);
+                    cached = true;
                 }
                 if (!cached) {
                     addViewHolderToRecycledViewPool(holder);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
new file mode 100644
index 0000000..f0225d3
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+@RunWith(AndroidJUnit4.class)
+public class RecyclerViewCacheTest {
+    RecyclerView mRecyclerView;
+    RecyclerView.Recycler mRecycler;
+    RecyclerView.ViewPrefetcher mViewPrefetcher;
+
+    @Before
+    public void setUp() throws Exception {
+        mRecyclerView = new RecyclerView(getContext());
+        mRecycler = mRecyclerView.mRecycler;
+        mViewPrefetcher = mRecyclerView.mViewPrefetcher;
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Test
+    public void prefetchReusesCacheItems() {
+        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
+            @Override
+            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+            }
+
+            @Override
+            int getItemPrefetchCount() {
+                return 3;
+            }
+
+            @Override
+            int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+                outIndices[0] = 0;
+                outIndices[1] = 1;
+                outIndices[2] = 2;
+                return 3;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            }
+        };
+        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
+
+        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+                    @Override
+                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+                            throws Throwable {
+                        return new RecyclerView.ViewHolder(new View(getContext())) {};
+                    }
+                });
+        when(mockAdapter.getItemCount()).thenReturn(10);
+        mRecyclerView.setAdapter(mockAdapter);
+
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+        mRecyclerView.layout(0, 0, 320, 320);
+
+        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
+        verify(mockAdapter, never()).onBindViewHolder(
+                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
+        assertTrue(mRecycler.mCachedViews.isEmpty());
+
+        // Prefetch multiple times...
+        for (int i = 0; i < 4; i++) {
+            int[] itemPrefetchArray = new int[] {-1, -1, -1};
+            int viewCount = prefetchingLayoutManager.gatherPrefetchIndices(1, 1,
+                    mRecyclerView.mState, itemPrefetchArray);
+            mRecycler.prefetch(itemPrefetchArray, viewCount);
+
+            // ...but should only see the same three items fetched/bound once each
+            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
+            verify(mockAdapter, times(3)).onBindViewHolder(
+                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
+
+            assertTrue(mRecycler.mCachedViews.size() == 3);
+            verifyCacheContainsPositions(0, 1, 2);
+        }
+    }
+
+    private void verifyCacheContainsPosition(int position) {
+        for (int i = 0; i < mRecycler.mCachedViews.size(); i++) {
+            if (mRecycler.mCachedViews.get(i).mPosition == position) return;
+        }
+        fail("Cache does not contain position " + position);
+    }
+
+    private void verifyCacheContainsPositions(Integer... positions) {
+        for (int i = 0; i < positions.length; i++) {
+            verifyCacheContainsPosition(positions[i]);
+        }
+    }
+
+    @Test
+    public void prefetchItemsNotEvictedWithInserts() {
+        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
+
+        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+                    @Override
+                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+                            throws Throwable {
+                        return new RecyclerView.ViewHolder(new View(getContext())) {};
+                    }
+                });
+        when(mockAdapter.getItemCount()).thenReturn(100);
+        mRecyclerView.setAdapter(mockAdapter);
+
+
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320);
+        mRecyclerView.layout(0, 0, 320, 320);
+
+        mViewPrefetcher.mItemPrefetchArray = new int[] { 0, 1, 2 };
+        mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, 3);
+        verifyCacheContainsPositions(0, 1, 2);
+
+        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
+        mRecycler.recycleView(mRecycler.getViewForPosition(10));
+        verifyCacheContainsPositions(0, 1, 2, 10);
+
+        mRecycler.recycleView(mRecycler.getViewForPosition(20));
+        verifyCacheContainsPositions(0, 1, 2, 10, 20);
+
+        mRecycler.recycleView(mRecycler.getViewForPosition(30));
+        verifyCacheContainsPositions(0, 1, 2, 20, 30);
+
+        mRecycler.recycleView(mRecycler.getViewForPosition(40));
+        verifyCacheContainsPositions(0, 1, 2, 30, 40);
+
+
+        // After clearing the cache, the prefetch priorities should be cleared as well:
+        mRecyclerView.mRecycler.recycleAndClearCachedViews();
+        for (int i : new int[] {0, 1, 2, 50, 60, 70, 80, 90}) {
+            mRecycler.recycleView(mRecycler.getViewForPosition(i));
+        }
+
+        // cache only contains most recent positions, no priority for previous prefetches:
+        verifyCacheContainsPositions(50, 60, 70, 80, 90);
+
+    }
+
+    @Test
+    public void prefetchItemsNotEvictedOnScroll() {
+        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
+
+        // 100x100 pixel views
+        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
+        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
+                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
+                    @Override
+                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
+                            throws Throwable {
+                        View view = new View(getContext());
+                        view.setMinimumWidth(100);
+                        view.setMinimumHeight(100);
+                        return new RecyclerView.ViewHolder(view) {};
+                    }
+                });
+        when(mockAdapter.getItemCount()).thenReturn(100);
+        mRecyclerView.setAdapter(mockAdapter);
+
+        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
+        mRecyclerView.setItemViewCacheSize(2);
+
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 200);
+        mRecyclerView.layout(0, 0, 300, 150);
+        mRecyclerView.scrollBy(0, 75);
+        assertTrue(mRecycler.mCachedViews.isEmpty());
+
+        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
+        mViewPrefetcher.mItemPrefetchArray = new int[] {-1, -1, -1};
+        int viewCount = mRecyclerView.getLayoutManager().gatherPrefetchIndices(0, 1,
+                mRecyclerView.mState, mViewPrefetcher.mItemPrefetchArray);
+        mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, viewCount);
+
+        // row 3 is cached:
+        verifyCacheContainsPositions(9, 10, 11);
+        assertTrue(mRecycler.mCachedViews.size() == 3);
+
+        // Scroll so 1 falls off (though 3 is still not on screen)
+        mRecyclerView.scrollBy(0, 50);
+
+        // row 3 is still cached, with a couple other recycled views:
+        verifyCacheContainsPositions(9, 10, 11);
+        assertTrue(mRecycler.mCachedViews.size() == 5);
+    }
+}
\ No newline at end of file