Use getDimensionPixelSize for reading divider height
am: 301061d

* commit '301061d5655864f03f9bab0a28f7c3f2e28a0a04':
  Use getDimensionPixelSize for reading divider height
diff --git a/Android.mk b/Android.mk
index f373f85..0d8e2f9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,7 +16,7 @@
 # Don't include in unbundled build.
 ifeq ($(TARGET_BUILD_APPS),)
 
-SUPPORT_CURRENT_SDK_VERSION := 23
+SUPPORT_CURRENT_SDK_VERSION := current
 SUPPORT_API_CHECK := $(LOCAL_PATH)/apicheck.mk
 api_check_current_msg_file := $(LOCAL_PATH)/apicheck_msg_current.txt
 api_check_last_msg_file := $(LOCAL_PATH)/apicheck_msg_last.txt
diff --git a/annotations/Android.mk b/annotations/Android.mk
index c741eae..df16cdb 100644
--- a/annotations/Android.mk
+++ b/annotations/Android.mk
@@ -18,4 +18,5 @@
 LOCAL_MODULE := android-support-annotations
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 6fdbc91..61d036b 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -54,6 +54,13 @@
     source    sourceSets.main.allJava
 }
 
+// Disable strict javadoc lint checks with javadoc v8 builds on Mac. http://b/26744780
+if (JavaVersion.current().isJava8Compatible()) {
+    tasks.withType(Javadoc) {
+        options.addBooleanOption('Xdoclint:none', true)
+    }
+}
+
 // custom tasks for creating source/javadoc jars
 task sourcesJar(type: Jar, dependsOn:classes) {
     classifier = 'sources'
diff --git a/annotations/src/android/support/annotation/AnimRes.java b/annotations/src/android/support/annotation/AnimRes.java
index 906461b..e91fa81 100644
--- a/annotations/src/android/support/annotation/AnimRes.java
+++ b/annotations/src/android/support/annotation/AnimRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ * to be an anim resource reference (e.g. {@code android.R.anim.fade_in}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AnimatorRes.java b/annotations/src/android/support/annotation/AnimatorRes.java
index 4681236..e969356 100644
--- a/annotations/src/android/support/annotation/AnimatorRes.java
+++ b/annotations/src/android/support/annotation/AnimatorRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ * to be an animator resource reference (e.g. {@code android.R.animator.fade_in}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/ArrayRes.java b/annotations/src/android/support/annotation/ArrayRes.java
index 347de36..758bce4 100644
--- a/annotations/src/android/support/annotation/ArrayRes.java
+++ b/annotations/src/android/support/annotation/ArrayRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ * to be an array resource reference (e.g. {@code android.R.array.phoneTypes}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AttrRes.java b/annotations/src/android/support/annotation/AttrRes.java
index 7ba1f0d..2034be5 100644
--- a/annotations/src/android/support/annotation/AttrRes.java
+++ b/annotations/src/android/support/annotation/AttrRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ * to be an attribute reference (e.g. {@code android.R.attr.action}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/BinderThread.java b/annotations/src/android/support/annotation/BinderThread.java
index 4dc4233..db7dd3a 100644
--- a/annotations/src/android/support/annotation/BinderThread.java
+++ b/annotations/src/android/support/annotation/BinderThread.java
@@ -30,10 +30,10 @@
  * on the binder thread.
  * <p>
  * Example:
- * <pre>{@code
- *  (&#64;BinderThread
+ * <pre><code>
+ *  &#64;BinderThread
  *  public BeamShareData createBeamShareData() { ... }
- * }</pre>
+ * </code></pre>
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/CallSuper.java b/annotations/src/android/support/annotation/CallSuper.java
index d321e87..9c01cdf 100644
--- a/annotations/src/android/support/annotation/CallSuper.java
+++ b/annotations/src/android/support/annotation/CallSuper.java
@@ -26,10 +26,10 @@
  * Denotes that any overriding methods should invoke this method as well.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;CallSuper
  *  public abstract void onFocusLost();
- * }</pre>
+ * </code></pre>
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/CheckResult.java b/annotations/src/android/support/annotation/CheckResult.java
index 2a11dd4..7f40676 100644
--- a/annotations/src/android/support/annotation/CheckResult.java
+++ b/annotations/src/android/support/annotation/CheckResult.java
@@ -30,7 +30,7 @@
  * <p>
  * Example:
  * <pre>{@code
- *  public &#64;CheckResult String trim(String s) { return s.trim(); }
+ *  public @CheckResult String trim(String s) { return s.trim(); }
  *  ...
  *  s.trim(); // this is probably an error
  *  s = s.trim(); // ok
diff --git a/annotations/src/android/support/annotation/ColorInt.java b/annotations/src/android/support/annotation/ColorInt.java
index 9b3cb7e..4fa46eb 100644
--- a/annotations/src/android/support/annotation/ColorInt.java
+++ b/annotations/src/android/support/annotation/ColorInt.java
@@ -31,7 +31,7 @@
  * <p>
  * Example:
  * <pre>{@code
- *  public abstract void setTextColor(&#64;ColorInt int color);
+ *  public abstract void setTextColor(@ColorInt int color);
  * }</pre>
  */
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/ColorRes.java b/annotations/src/android/support/annotation/ColorRes.java
index eb273c4..d0fd49a 100644
--- a/annotations/src/android/support/annotation/ColorRes.java
+++ b/annotations/src/android/support/annotation/ColorRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a color resource reference (e.g. {@link android.R.color#black}).
+ * to be a color resource reference (e.g. {@code android.R.color.black}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/DimenRes.java b/annotations/src/android/support/annotation/DimenRes.java
index c3492a5..08dc4a9 100644
--- a/annotations/src/android/support/annotation/DimenRes.java
+++ b/annotations/src/android/support/annotation/DimenRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ * to be a dimension resource reference (e.g. {@code android.R.dimen.app_icon_size}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/DrawableRes.java b/annotations/src/android/support/annotation/DrawableRes.java
index 0ea1bca..f130380 100644
--- a/annotations/src/android/support/annotation/DrawableRes.java
+++ b/annotations/src/android/support/annotation/DrawableRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ * to be a drawable resource reference (e.g. {@code android.R.attr.alertDialogIcon}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/FloatRange.java b/annotations/src/android/support/annotation/FloatRange.java
index 2544388..275b5b6 100644
--- a/annotations/src/android/support/annotation/FloatRange.java
+++ b/annotations/src/android/support/annotation/FloatRange.java
@@ -29,12 +29,12 @@
  * Denotes that the annotated element should be a float or double in the given range
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;FloatRange(from=0.0,to=1.0)
  *  public float getAlpha() {
  *      ...
  *  }
- * }</pre>
+ * </code></pre>
  */
 @Retention(CLASS)
 @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/IdRes.java b/annotations/src/android/support/annotation/IdRes.java
index 9a0060f..7cb3f98 100644
--- a/annotations/src/android/support/annotation/IdRes.java
+++ b/annotations/src/android/support/annotation/IdRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an id resource reference (e.g. {@link android.R.id#copy}).
+ * to be an id resource reference (e.g. {@code android.R.id.copy}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index fedd7b4..be2e2b8 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -32,24 +32,24 @@
  * multiple constants can be combined.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;Retention(SOURCE)
- *  &#64;IntDef(&#123;NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS&#125;)
- *  public &#64;interface NavigationMode &#123;&#125;
+ *  &#64;IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
  *  public static final int NAVIGATION_MODE_STANDARD = 0;
  *  public static final int NAVIGATION_MODE_LIST = 1;
  *  public static final int NAVIGATION_MODE_TABS = 2;
  *  ...
- *  public abstract void setNavigationMode(&#64;NavigationMode int mode);
+ *  public abstract void setNavigationMode(@NavigationMode int mode);
  *  &#64;NavigationMode
  *  public abstract int getNavigationMode();
- * }</pre>
+ * </code></pre>
  * For a flag, set the flag attribute:
- * <pre>{@code
+ * <pre><code>
  *  &#64;IntDef(
  *      flag = true
- *      value = &#123;NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS&#125;)
- * }</pre>
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
  */
 @Retention(SOURCE)
 @Target({ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/IntRange.java b/annotations/src/android/support/annotation/IntRange.java
index 6011276..d489acb 100644
--- a/annotations/src/android/support/annotation/IntRange.java
+++ b/annotations/src/android/support/annotation/IntRange.java
@@ -18,6 +18,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
 import static java.lang.annotation.ElementType.METHOD;
@@ -28,15 +29,15 @@
  * Denotes that the annotated element should be an int or long in the given range
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;IntRange(from=0,to=255)
  *  public int getAlpha() {
  *      ...
  *  }
- * }</pre>
+ * </code></pre>
  */
 @Retention(CLASS)
-@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
 public @interface IntRange {
     /** Smallest value, inclusive */
     long from() default Long.MIN_VALUE;
diff --git a/annotations/src/android/support/annotation/IntegerRes.java b/annotations/src/android/support/annotation/IntegerRes.java
index 6bfcc37..d965675 100644
--- a/annotations/src/android/support/annotation/IntegerRes.java
+++ b/annotations/src/android/support/annotation/IntegerRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ * to be an integer resource reference (e.g. {@code android.R.integer.config_shortAnimTime}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/InterpolatorRes.java b/annotations/src/android/support/annotation/InterpolatorRes.java
index 20f42b8..3195c57 100644
--- a/annotations/src/android/support/annotation/InterpolatorRes.java
+++ b/annotations/src/android/support/annotation/InterpolatorRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ * to be an interpolator resource reference (e.g. {@code android.R.interpolator.cycle}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/Keep.java b/annotations/src/android/support/annotation/Keep.java
index de1c9e5..111971c 100644
--- a/annotations/src/android/support/annotation/Keep.java
+++ b/annotations/src/android/support/annotation/Keep.java
@@ -33,12 +33,12 @@
  * so a compiler may think that the code is unused.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;Keep
  *  public void foo() {
  *      ...
  *  }
- * }</pre>
+ * </code></pre>
  */
 @Retention(CLASS)
 @Target({PACKAGE,TYPE,ANNOTATION_TYPE,CONSTRUCTOR,METHOD,FIELD})
diff --git a/annotations/src/android/support/annotation/LayoutRes.java b/annotations/src/android/support/annotation/LayoutRes.java
index ad04ef0..0de4e84 100644
--- a/annotations/src/android/support/annotation/LayoutRes.java
+++ b/annotations/src/android/support/annotation/LayoutRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a layout resource reference (e.g. {@link android.R.layout#list_content}).
+ * to be a layout resource reference (e.g. {@code android.R.layout.list_content}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index cff464f..1ce8b3c 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -30,10 +30,10 @@
  * on the main thread.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;MainThread
  *  public void deliverResult(D data) { ... }
- * }</pre>
+ * </code></pre>
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/RequiresPermission.java b/annotations/src/android/support/annotation/RequiresPermission.java
index bb9afb2..1ff1964 100644
--- a/annotations/src/android/support/annotation/RequiresPermission.java
+++ b/annotations/src/android/support/annotation/RequiresPermission.java
@@ -22,40 +22,59 @@
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
 /**
  * Denotes that the annotated element requires (or may require) one or more permissions.
- * <p/>
+ * <p>
  * Example of requiring a single permission:
- * <pre>{@code
+ * <pre><code>
  *   &#64;RequiresPermission(Manifest.permission.SET_WALLPAPER)
  *   public abstract void setWallpaper(Bitmap bitmap) throws IOException;
  *
  *   &#64;RequiresPermission(ACCESS_COARSE_LOCATION)
  *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
  * Example of requiring at least one permission from a set:
- * <pre>{@code
+ * <pre><code>
  *   &#64;RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
  *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
  * Example of requiring multiple permissions:
- * <pre>{@code
+ * <pre><code>
  *   &#64;RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
  *   public abstract Location getLastKnownLocation(String provider);
- * }</pre>
+ * </code></pre>
  * Example of requiring separate read and write permissions for a content provider:
- * <pre>{@code
- *   &#64;RequiresPermission.Read(&#64;RequiresPermission(READ_HISTORY_BOOKMARKS))
- *   &#64;RequiresPermission.Write(&#64;RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ * <pre><code>
+ *   &#64;RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ *   &#64;RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
  *   public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * </code></pre>
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter. For example, consider
+ * {@code android.app.Activity.startActivity(android.content.Intent)}:
+ * <pre>{@code
+ *   public void startActivity(@RequiresPermission Intent intent) { ... }
  * }</pre>
- *
- * @hide
+ * Notice how there are no actual permission names listed in the annotation. The actual
+ * permissions required will depend on the particular intent passed in. For example,
+ * the code may look like this:
+ * <pre>{@code
+ *   Intent intent = new Intent(Intent.ACTION_CALL);
+ *   startActivity(intent);
+ * }</pre>
+ * and the actual permission requirement for this particular intent is described on
+ * the Intent name itself:
+ * <pre><code>
+ *   &#64;RequiresPermission(Manifest.permission.CALL_PHONE)
+ *   public static final String ACTION_CALL = "android.intent.action.CALL";
+ * </code></pre>
  */
 @Retention(CLASS)
-@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD})
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
 public @interface RequiresPermission {
     /**
      * The name of the permission that is required, if precisely one permission
@@ -87,18 +106,28 @@
     boolean conditional() default false;
 
     /**
-     * Specifies that the given permission is required for read operations
+     * Specifies that the given permission is required for read operations.
+     * <p>
+     * When specified on a parameter, the annotation indicates that the method requires
+     * a permission which depends on the value of the parameter (and typically
+     * the corresponding field passed in will be one of a set of constants which have
+     * been annotated with a {@code @RequiresPermission} annotation.)
      */
-    @Target(FIELD)
+    @Target({FIELD, METHOD, PARAMETER})
     @interface Read {
-        RequiresPermission value();
+        RequiresPermission value() default @RequiresPermission;
     }
 
     /**
-     * Specifies that the given permission is required for write operations
+     * Specifies that the given permission is required for write operations.
+     * <p>
+     * When specified on a parameter, the annotation indicates that the method requires
+     * a permission which depends on the value of the parameter (and typically
+     * the corresponding field passed in will be one of a set of constants which have
+     * been annotated with a {@code @RequiresPermission} annotation.)
      */
-    @Target(FIELD)
+    @Target({FIELD, METHOD, PARAMETER})
     @interface Write {
-        RequiresPermission value();
+        RequiresPermission value() default @RequiresPermission;
     }
 }
diff --git a/annotations/src/android/support/annotation/Size.java b/annotations/src/android/support/annotation/Size.java
index 5903890..b1c0308 100644
--- a/annotations/src/android/support/annotation/Size.java
+++ b/annotations/src/android/support/annotation/Size.java
@@ -32,7 +32,7 @@
  * <p>
  * Example:
  * <pre>{@code
- *  public void getLocationInWindow(&#64;Size(2) int[] location) {
+ *  public void getLocationInWindow(@Size(2) int[] location) {
  *      ...
  *  }
  * }</pre>
diff --git a/annotations/src/android/support/annotation/StringDef.java b/annotations/src/android/support/annotation/StringDef.java
index cae227e..33f71e9 100644
--- a/annotations/src/android/support/annotation/StringDef.java
+++ b/annotations/src/android/support/annotation/StringDef.java
@@ -29,20 +29,20 @@
  * type and that its value should be one of the explicitly named constants.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;Retention(SOURCE)
- *  &#64;StringDef(&#123;
+ *  &#64;StringDef({
  *     POWER_SERVICE,
  *     WINDOW_SERVICE,
  *     LAYOUT_INFLATER_SERVICE
- *  &#125;)
- *  public &#64;interface ServiceName &#123;&#125;
+ *  })
+ *  public @interface ServiceName {}
  *  public static final String POWER_SERVICE = "power";
  *  public static final String WINDOW_SERVICE = "window";
  *  public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
  *  ...
- *  public abstract Object getSystemService(&#64;ServiceName String name);
- * }</pre>
+ *  public abstract Object getSystemService(@ServiceName String name);
+ * </code></pre>
  */
 @Retention(SOURCE)
 @Target({ANNOTATION_TYPE})
diff --git a/annotations/src/android/support/annotation/StringRes.java b/annotations/src/android/support/annotation/StringRes.java
index 2c1149c..cf427f6 100644
--- a/annotations/src/android/support/annotation/StringRes.java
+++ b/annotations/src/android/support/annotation/StringRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a String resource reference (e.g. {@link android.R.string#ok}).
+ * to be a String resource reference (e.g. {@code android.R.string.ok}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleRes.java b/annotations/src/android/support/annotation/StyleRes.java
index 6d931bf..ca7a187 100644
--- a/annotations/src/android/support/annotation/StyleRes.java
+++ b/annotations/src/android/support/annotation/StyleRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ * to be a style resource reference (e.g. {@code android.R.style.TextAppearance}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleableRes.java b/annotations/src/android/support/annotation/StyleableRes.java
index d7902d1..6e4d97f 100644
--- a/annotations/src/android/support/annotation/StyleableRes.java
+++ b/annotations/src/android/support/annotation/StyleableRes.java
@@ -27,7 +27,7 @@
 
 /**
  * Denotes that an integer parameter, field or method return value is expected
- * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ * to be a styleable resource reference (e.g. {@code android.R.styleable.TextView_text}).
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index 98318b4..98d35f9 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -30,11 +30,11 @@
  * on the UI thread.
  * <p>
  * Example:
- * <pre>{@code
+ * <pre><code>
  *  &#64;UiThread
  *
- *  public abstract void setText(&#64;NonNull String text) { ... }
- * }</pre>
+ *  public abstract void setText(@NonNull String text) { ... }
+ * </code></pre>
  */
 @Documented
 @Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/WorkerThread.java b/annotations/src/android/support/annotation/WorkerThread.java
index 2919286..a5aabd7 100644
--- a/annotations/src/android/support/annotation/WorkerThread.java
+++ b/annotations/src/android/support/annotation/WorkerThread.java
@@ -30,10 +30,10 @@
  * on a worker thread.
  * <p>
  * Example:
- * <pre>{@code
- *  (&#64;WorkerThread
+ * <pre><code>
+ *  &#64;WorkerThread
  *  protected abstract FilterResults performFiltering(CharSequence constraint);
- * }</pre>
+ * </code></pre>
  */
 @Documented
 @Retention(CLASS)
diff --git a/apicheck.mk b/apicheck.mk
index 9644f56..7f56866 100644
--- a/apicheck.mk
+++ b/apicheck.mk
@@ -46,8 +46,9 @@
     $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),$(support_module),,COMMON)/src
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 
+LOCAL_DROIDDOC_STUB_OUT_DIR := $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/src
+
 LOCAL_DROIDDOC_OPTIONS:= \
-    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/src \
     -stubpackages "$(subst $(space),:,$(support_module_java_packages))" \
     -api $(support_module_api_file) \
     -removedApi $(support_module_removed_file) \
diff --git a/build.gradle b/build.gradle
index 1d4712b..9aa0557 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,3 +1,6 @@
+import com.android.build.gradle.internal.coverage.JacocoReportTask
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
+
 buildscript {
     repositories {
         maven { url '../../prebuilts/gradle-plugin' }
@@ -10,8 +13,8 @@
     }
 }
 
-ext.supportVersion = '23.2.1'
-ext.extraVersion = 28
+ext.supportVersion = '24.0.0-SNAPSHOT'
+ext.extraVersion = 25
 ext.supportRepoOut = ''
 ext.buildToolsVersion = '23.0.2'
 ext.buildNumber = Integer.toString(ext.extraVersion)
@@ -183,6 +186,9 @@
     project.plugins.whenPluginAdded { plugin ->
         if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
             project.android.buildToolsVersion = rootProject.buildToolsVersion
+            // enable code coverage for debug builds
+            // run createDebugCoverageReport to get the coverage
+            project.android.buildTypes.debug.testCoverageEnabled = true
         }
     }
 
@@ -202,6 +208,26 @@
             }
         }
     }
+
+    project.afterEvaluate { p ->
+        // remove dependency on the test so that we still get coverage even if some tests fail
+        p.tasks.findAll { it instanceof JacocoReportTask}.each { task ->
+            def toBeRemoved = new ArrayList()
+            def dependencyList = task.taskDependencies.values
+            dependencyList.each { dep ->
+                if (dep instanceof String) {
+                    def t = tasks.findByName(dep)
+                    if (t instanceof DeviceProviderInstrumentTestTask) {
+                        toBeRemoved.add(dep)
+                        task.mustRunAfter(t)
+                    }
+                }
+            }
+            toBeRemoved.each { dep ->
+                dependencyList.remove(dep)
+            }
+        }
+    }
 }
 
 project.gradle.buildFinished { buildResult ->
diff --git a/customtabs/Android.mk b/customtabs/Android.mk
index 562463d..fb2f354 100644
--- a/customtabs/Android.mk
+++ b/customtabs/Android.mk
@@ -26,6 +26,7 @@
     $(call all-Iaidl-files-under, src)
 LOCAL_JAVA_LIBRARIES := android-support-annotations \
 android-support-v4
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index 4cbfb88..65a3e6f 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -5,11 +5,23 @@
 dependencies {
     compile project(':support-v4')
     compile project(':support-annotations')
+
+    androidTestCompile ('com.android.support.test:runner:0.4.1') {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+        exclude module: 'support-annotations'
+    }
+    testCompile 'junit:junit:4.12'
 }
 
 android {
     compileSdkVersion project.ext.currentSdk
 
+    defaultConfig {
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
         main.java.srcDirs = ['src']
diff --git a/customtabs/tests/src/AndroidManifest.xml b/customtabs/tests/src/AndroidManifest.xml
new file mode 100644
index 0000000..d87a83f
--- /dev/null
+++ b/customtabs/tests/src/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.support.customtabs.test">
+    <uses-sdk
+        android:minSdkVersion="15"
+        android:targetSdkVersion="23"
+        tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+              android.support.test.espresso, android.support.test.espresso.idling" />
+
+    <application/>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.customtabs.test"/>
+</manifest>
diff --git a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
index 1a72789..2431245 100644
--- a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
+++ b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
@@ -18,15 +18,23 @@
 
 import android.content.Intent;
 import android.graphics.Color;
-import android.test.AndroidTestCase;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
 
 /**
  * Tests for CustomTabsIntent.
  */
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class CustomTabsIntentTest extends AndroidTestCase {
-
+public class CustomTabsIntentTest {
+    @Test
     public void testBareboneCustomTabIntent() {
         CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
         Intent intent = customTabsIntent.intent;
@@ -35,10 +43,13 @@
 
         assertEquals(Intent.ACTION_VIEW, intent.getAction());
         assertTrue(intent.hasExtra(CustomTabsIntent.EXTRA_SESSION));
-        assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+        if (Build.VERSION.SDK_INT >= 18) {
+            assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+        }
         assertNull(intent.getComponent());
     }
 
+    @Test
     public void testToolbarColor() {
         int color = Color.RED;
         Intent intent = new CustomTabsIntent.Builder().setToolbarColor(color).build().intent;
@@ -46,9 +57,10 @@
         assertEquals(color, intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
     }
 
+    @Test
     public void testToolbarColorIsNotAResource() {
-        int colorId = android.R.color.background_dark;
-        int color = getContext().getResources().getColor(colorId);
+        @ColorRes int colorId = android.R.color.background_dark;
+        int color = InstrumentationRegistry.getContext().getResources().getColor(colorId);
         Intent intent = new CustomTabsIntent.Builder().setToolbarColor(colorId).build().intent;
         assertFalse("The color should not be a resource ID",
                 color == intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
diff --git a/design/Android.mk b/design/Android.mk
index be3c734..c4004f5 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -28,8 +28,10 @@
 LOCAL_AAPT_FLAGS := \
     --auto-add-overlay \
     --extra-packages android.support.v7.appcompat \
-    --extra-packages android.support.v7.recyclerview
+    --extra-packages android.support.v7.recyclerview \
+    --no-version-vectors
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -44,6 +46,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -58,6 +61,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -72,6 +76,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -86,6 +91,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -100,6 +106,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -114,6 +121,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -131,6 +139,7 @@
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/design/api/current.txt b/design/api/current.txt
index 18c6056..eceb522 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -378,6 +378,7 @@
     ctor public TabLayout(android.content.Context);
     ctor public TabLayout(android.content.Context, android.util.AttributeSet);
     ctor public TabLayout(android.content.Context, android.util.AttributeSet, int);
+    method public void addOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
     method public void addTab(android.support.design.widget.TabLayout.Tab);
     method public void addTab(android.support.design.widget.TabLayout.Tab, int);
     method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
@@ -390,9 +391,10 @@
     method public android.content.res.ColorStateList getTabTextColors();
     method public android.support.design.widget.TabLayout.Tab newTab();
     method public void removeAllTabs();
+    method public void removeOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
     method public void removeTab(android.support.design.widget.TabLayout.Tab);
     method public void removeTabAt(int);
-    method public void setOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
+    method public deprecated void setOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
     method public void setScrollPosition(int, float, boolean);
     method public void setSelectedTabIndicatorColor(int);
     method public void setSelectedTabIndicatorHeight(int);
@@ -402,6 +404,7 @@
     method public void setTabTextColors(int, int);
     method public deprecated void setTabsFromPagerAdapter(android.support.v4.view.PagerAdapter);
     method public void setupWithViewPager(android.support.v4.view.ViewPager);
+    method public void setupWithViewPager(android.support.v4.view.ViewPager, boolean);
     field public static final int GRAVITY_CENTER = 1; // 0x1
     field public static final int GRAVITY_FILL = 0; // 0x0
     field public static final int MODE_FIXED = 1; // 0x1
diff --git a/design/build.gradle b/design/build.gradle
index eb880a1..7148e50 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -25,6 +25,8 @@
     defaultConfig {
         minSdkVersion 7
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        // This disables the builds tools automatic vector -> PNG generation
+        generatedDensities = []
     }
 
     sourceSets {
@@ -53,6 +55,10 @@
     buildTypes.all {
         consumerProguardFiles 'proguard-rules.pro'
     }
+
+    aaptOptions {
+        additionalParameters "--no-version-vectors"
+    }
 }
 
 android.libraryVariants.all { variant ->
diff --git a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
index 1c708f5..b4a0a57 100644
--- a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
@@ -59,6 +59,9 @@
         mStartTime = SystemClock.uptimeMillis();
         mIsRunning = true;
 
+        // Reset the animated fraction
+        mAnimatedFraction = 0f;
+
         if (mListener != null) {
             mListener.onAnimationStart();
         }
@@ -120,6 +123,7 @@
 
         if (mListener != null) {
             mListener.onAnimationCancel();
+            mListener.onAnimationEnd();
         }
     }
 
@@ -156,7 +160,7 @@
         if (mIsRunning) {
             // Update the animated fraction
             final long elapsed = SystemClock.uptimeMillis() - mStartTime;
-            final float linearFraction = elapsed / (float) mDuration;
+            final float linearFraction = MathUtils.constrain(elapsed / (float) mDuration, 0f, 1f);
             mAnimatedFraction = mInterpolator != null
                     ? mInterpolator.getInterpolation(linearFraction)
                     : linearFraction;
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 45b0bba..2419de9 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -674,7 +674,7 @@
         private boolean mSkipNestedPreScroll;
         private boolean mWasNestedFlung;
 
-        private ValueAnimatorCompat mAnimator;
+        private ValueAnimatorCompat mOffsetAnimator;
 
         private int mOffsetToChildIndexOnLayout = INVALID_POSITION;
         private boolean mOffsetToChildIndexOnLayoutIsMinHeight;
@@ -698,9 +698,9 @@
                     && child.hasScrollableChildren()
                     && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
 
-            if (started && mAnimator != null) {
+            if (started && mOffsetAnimator != null) {
                 // Cancel any offset animation
-                mAnimator.cancel();
+                mOffsetAnimator.cancel();
             }
 
             // A new nested scroll has started so clear out the previous ref
@@ -811,16 +811,16 @@
                 final AppBarLayout child, final int offset) {
             final int currentOffset = getTopBottomOffsetForScrollingSibling();
             if (currentOffset == offset) {
-                if (mAnimator != null && mAnimator.isRunning()) {
-                    mAnimator.cancel();
+                if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
+                    mOffsetAnimator.cancel();
                 }
                 return;
             }
 
-            if (mAnimator == null) {
-                mAnimator = ViewUtils.createAnimator();
-                mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
-                mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+            if (mOffsetAnimator == null) {
+                mOffsetAnimator = ViewUtils.createAnimator();
+                mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
+                mOffsetAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
                     @Override
                     public void onAnimationUpdate(ValueAnimatorCompat animator) {
                         setHeaderTopBottomOffset(coordinatorLayout, child,
@@ -828,16 +828,17 @@
                     }
                 });
             } else {
-                mAnimator.cancel();
+                mOffsetAnimator.cancel();
             }
 
             // Set the duration based on the amount of dips we're travelling in
             final float distanceDp = Math.abs(currentOffset - offset) /
                     coordinatorLayout.getResources().getDisplayMetrics().density;
-            mAnimator.setDuration(Math.round(distanceDp * 1000 / ANIMATE_OFFSET_DIPS_PER_SECOND));
+            mOffsetAnimator.setDuration(
+                    Math.round(distanceDp * 1000 / ANIMATE_OFFSET_DIPS_PER_SECOND));
 
-            mAnimator.setIntValues(currentOffset, offset);
-            mAnimator.start();
+            mOffsetAnimator.setIntValues(currentOffset, offset);
+            mOffsetAnimator.start();
         }
 
         private View getChildOnOffset(AppBarLayout abl, final int offset) {
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 100150e..17fada5 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -386,19 +386,26 @@
                     && mDummyView.getVisibility() == VISIBLE;
 
             if (mDrawCollapsingTitle) {
+                final boolean isRtl = ViewCompat.getLayoutDirection(this)
+                        == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+                // Update the collapsed bounds
                 int bottomOffset = 0;
                 if (mToolbarDirectChild != null && mToolbarDirectChild != this) {
                     final LayoutParams lp = (LayoutParams) mToolbarDirectChild.getLayoutParams();
                     bottomOffset = lp.bottomMargin;
                 }
                 ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
-                mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left,
-                        bottom - mTmpRect.height() - bottomOffset,
-                        mTmpRect.right,
-                        bottom - bottomOffset);
+                mCollapsingTextHelper.setCollapsedBounds(
+                        mTmpRect.left + (isRtl
+                                ? mToolbar.getTitleMarginEnd()
+                                : mToolbar.getTitleMarginStart()),
+                        bottom + mToolbar.getTitleMarginTop() - mTmpRect.height() - bottomOffset,
+                        mTmpRect.right + (isRtl
+                                ? mToolbar.getTitleMarginStart()
+                                : mToolbar.getTitleMarginEnd()),
+                        bottom - bottomOffset - mToolbar.getTitleMarginBottom());
 
-                final boolean isRtl = ViewCompat.getLayoutDirection(this)
-                        == ViewCompat.LAYOUT_DIRECTION_RTL;
                 // Update the expanded bounds
                 mCollapsingTextHelper.setExpandedBounds(
                         isRtl ? mExpandedMarginEnd : mExpandedMarginStart,
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 732e48a..cba8759 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -35,6 +35,7 @@
 import android.support.v7.widget.AppCompatImageHelper;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -97,6 +98,7 @@
 
     private boolean mCompatPadding;
     private final Rect mShadowPadding = new Rect();
+    private final Rect mTouchArea = new Rect();
 
     private AppCompatImageHelper mImageHelper;
 
@@ -448,8 +450,17 @@
         }
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if(getContentRect(mTouchArea) && !mTouchArea.contains((int) ev.getX(), (int) ev.getY())) {
+            return false;
+        }
+
+        return super.onTouchEvent(ev);
+    }
+
     /**
-     * Behavior designed for use with {@link FloatingActionButton} instances. It's main function
+     * Behavior designed for use with {@link FloatingActionButton} instances. Its main function
      * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
      * not cover them.
      */
@@ -493,7 +504,7 @@
             }
 
             if (child.getUserSetVisibility() != VISIBLE) {
-                // The view isn't set to be visible so skip changing it's visibility
+                // The view isn't set to be visible so skip changing its visibility
                 return false;
             }
 
@@ -532,7 +543,7 @@
 
             if (fab.isShown()
                     && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
-                // If the FAB will be travelling by more than 2/3 of it's height, let's animate
+                // If the FAB will be travelling by more than 2/3 of its height, let's animate
                 // it instead
                 if (mFabTranslationYAnimator == null) {
                     mFabTranslationYAnimator = ViewUtils.createAnimator();
@@ -575,7 +586,7 @@
         @Override
         public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
                 int layoutDirection) {
-            // First, lets make sure that the visibility of the FAB is consistent
+            // First, let's make sure that the visibility of the FAB is consistent
             final List<View> dependencies = parent.getDependencies(child);
             for (int i = 0, count = dependencies.size(); i < count; i++) {
                 final View dependency = dependencies.get(i);
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index 8e0c57b..657e9ce 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.support.annotation.ColorInt;
 import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.StringRes;
 import android.support.design.R;
@@ -121,6 +122,7 @@
      * @hide
      */
     @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
+    @IntRange(from = 1)
     @Retention(RetentionPolicy.SOURCE)
     public @interface Duration {}
 
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 18969de..1059b9a 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -241,7 +241,9 @@
     private int mTabGravity;
     private int mMode;
 
-    private OnTabSelectedListener mOnTabSelectedListener;
+    private OnTabSelectedListener mSelectedListener;
+    private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
+    private OnTabSelectedListener mCurrentVpSelectedListener;
 
     private ValueAnimatorCompat mScrollAnimator;
 
@@ -474,13 +476,47 @@
     }
 
     /**
-     * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will
-     * handle switching to and from tabs.
-     *
-     * @param onTabSelectedListener Listener to handle tab selection events
+     * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and
+     * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
      */
-    public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
-        mOnTabSelectedListener = onTabSelectedListener;
+    @Deprecated
+    public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) {
+        // The logic in this method emulates what we had before support for multiple
+        // registered listeners.
+        if (mSelectedListener != null) {
+            removeOnTabSelectedListener(mSelectedListener);
+        }
+        // Update the deprecated field so that we can remove the passed listener the next
+        // time we're called
+        mSelectedListener = listener;
+        if (listener != null) {
+            addOnTabSelectedListener(listener);
+        }
+    }
+
+    /**
+     * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection
+     * changes.
+     *
+     * <p>Components that add a listener should take care to remove it when finished via
+     * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p>
+     *
+     * @param listener listener to add
+     */
+    public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
+        if (!mSelectedListeners.contains(listener)) {
+            mSelectedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via
+     * {@link #addOnTabSelectedListener(OnTabSelectedListener)}.
+     *
+     * @param listener listener to remove
+     */
+    public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
+        mSelectedListeners.remove(listener);
     }
 
     /**
@@ -675,25 +711,45 @@
     /**
      * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
      *
-     * <p>This method will link the given ViewPager and this TabLayout together so that any
-     * changes in one are automatically reflected in the other. This includes adapter changes,
-     * scroll state changes, and clicks. The tabs displayed in this layout will be populated
+     * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with
+     * auto-refresh enabled.</p>
+     *
+     * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
+     */
+    public void setupWithViewPager(@Nullable ViewPager viewPager) {
+        setupWithViewPager(viewPager, true);
+    }
+
+    /**
+     * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
+     *
+     * <p>This method will link the given ViewPager and this TabLayout together so that
+     * changes in one are automatically reflected in the other. This includes scroll state changes
+     * and clicks. The tabs displayed in this layout will be populated
      * from the ViewPager adapter's page titles.</p>
      *
-     * <p>After this method is called, you will not need this method again unless you want
-     * to change the linked ViewPager.</p>
+     * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will
+     * trigger this layout to re-populate itself from the adapter's titles.</p>
      *
      * <p>If the given ViewPager is non-null, it needs to already have a
      * {@link PagerAdapter} set.</p>
      *
-     * @param viewPager The ViewPager to link, or {@code null} to clear any previous link.
+     * @param viewPager   the ViewPager to link to, or {@code null} to clear any previous link
+     * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's
+     *                    content changes
      */
-    public void setupWithViewPager(@Nullable final ViewPager viewPager) {
+    public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
         if (mViewPager != null && mPageChangeListener != null) {
             // If we've already been setup with a ViewPager, remove us from it
             mViewPager.removeOnPageChangeListener(mPageChangeListener);
         }
 
+        if (mCurrentVpSelectedListener != null) {
+            // If we already have a tab selected listener for the ViewPager, remove it
+            removeOnTabSelectedListener(mCurrentVpSelectedListener);
+            mCurrentVpSelectedListener = null;
+        }
+
         if (viewPager != null) {
             final PagerAdapter adapter = viewPager.getAdapter();
             if (adapter == null) {
@@ -710,16 +766,20 @@
             viewPager.addOnPageChangeListener(mPageChangeListener);
 
             // Now we'll add a tab selected listener to set ViewPager's current item
-            setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
+            mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
+            addOnTabSelectedListener(mCurrentVpSelectedListener);
 
-            // Now we'll populate ourselves from the pager adapter
-            setPagerAdapter(adapter, true);
+            // Now we'll populate ourselves from the pager adapter, adding an observer is
+            // autoRefresh is enabled
+            setPagerAdapter(adapter, autoRefresh);
+
+            // Now update the scroll position to match the ViewPager's current item
+            setScrollPosition(viewPager.getCurrentItem(), 0f, true);
         } else {
             // We've been given a null ViewPager so we need to clear out the internal state,
             // listeners and observers
             mViewPager = null;
-            setOnTabSelectedListener(null);
-            setPagerAdapter(null, true);
+            setPagerAdapter(null, false);
         }
     }
 
@@ -780,8 +840,6 @@
                     selectTab(getTabAt(curItem));
                 }
             }
-        } else {
-            removeAllTabs();
         }
     }
 
@@ -995,12 +1053,12 @@
         selectTab(tab, true);
     }
 
-    void selectTab(Tab tab, boolean updateIndicator) {
-        if (mSelectedTab == tab) {
-            if (mSelectedTab != null) {
-                if (mOnTabSelectedListener != null) {
-                    mOnTabSelectedListener.onTabReselected(mSelectedTab);
-                }
+    void selectTab(final Tab tab, boolean updateIndicator) {
+        final Tab currentTab = mSelectedTab;
+
+        if (currentTab == tab) {
+            if (currentTab != null) {
+                dispatchTabReselected(tab);
                 animateToTab(tab.getPosition());
             }
         } else {
@@ -1009,7 +1067,7 @@
                 if (newPosition != Tab.INVALID_POSITION) {
                     setSelectedTabView(newPosition);
                 }
-                if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
+                if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION)
                         && newPosition != Tab.INVALID_POSITION) {
                     // If we don't currently have a tab, just draw the indicator
                     setScrollPosition(newPosition, 0f, true);
@@ -1017,13 +1075,27 @@
                     animateToTab(newPosition);
                 }
             }
-            if (mSelectedTab != null && mOnTabSelectedListener != null) {
-                mOnTabSelectedListener.onTabUnselected(mSelectedTab);
-            }
+            dispatchTabUnselected(currentTab);
             mSelectedTab = tab;
-            if (mSelectedTab != null && mOnTabSelectedListener != null) {
-                mOnTabSelectedListener.onTabSelected(mSelectedTab);
-            }
+            dispatchTabSelected(tab);
+        }
+    }
+
+    private void dispatchTabSelected(@NonNull final Tab tab) {
+        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+            mSelectedListeners.get(i).onTabSelected(tab);
+        }
+    }
+
+    private void dispatchTabUnselected(@NonNull final Tab tab) {
+        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+            mSelectedListeners.get(i).onTabUnselected(tab);
+        }
+    }
+
+    private void dispatchTabReselected(@NonNull final Tab tab) {
+        for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+            mSelectedListeners.get(i).onTabReselected(tab);
         }
     }
 
@@ -1150,6 +1222,10 @@
         public Tab setCustomView(@Nullable View view) {
             mCustomView = view;
             updateView();
+
+            final boolean isSelected = (mParent.getSelectedTabPosition() == getPosition());
+            mCustomView.setSelected(isSelected);
+
             return this;
         }
 
@@ -1951,14 +2027,14 @@
         }
 
         @Override
-        public void onPageScrollStateChanged(int state) {
+        public void onPageScrollStateChanged(final int state) {
             mPreviousScrollState = mScrollState;
             mScrollState = state;
         }
 
         @Override
-        public void onPageScrolled(int position, float positionOffset,
-                int positionOffsetPixels) {
+        public void onPageScrolled(final int position, final float positionOffset,
+                final int positionOffsetPixels) {
             final TabLayout tabLayout = mTabLayoutRef.get();
             if (tabLayout != null) {
                 // Only update the text selection if we're not settling, or we are settling after
@@ -1975,9 +2051,10 @@
         }
 
         @Override
-        public void onPageSelected(int position) {
+        public void onPageSelected(final int position) {
             final TabLayout tabLayout = mTabLayoutRef.get();
-            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position) {
+            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
+                    && position < tabLayout.getTabCount()) {
                 // Select the tab, only updating the indicator if we're not being dragged/settled
                 // (since onPageScrolled will handle that).
                 final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 646ee2f..48b1152 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -52,6 +52,7 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+import android.widget.Toast;
 
 /**
  * Layout which wraps an {@link android.widget.EditText} (or descendant) to show a floating label
@@ -529,11 +530,6 @@
      * @see #getError()
      */
     public void setError(@Nullable final CharSequence error) {
-        if (TextUtils.equals(mError, error)) {
-            // If we already have the same error, ignore
-            return;
-        }
-
         mError = error;
 
         if (!mErrorEnabled) {
@@ -545,8 +541,9 @@
             setErrorEnabled(true);
         }
 
-        // Only animate if we've been laid out already
-        final boolean animate = ViewCompat.isLaidOut(this);
+        // Only animate if we've been laid out already and we have a different error
+        final boolean animate = ViewCompat.isLaidOut(this)
+                && !TextUtils.equals(mErrorView.getText(), error);
         mErrorShown = !TextUtils.isEmpty(error);
 
         if (mErrorShown) {
@@ -568,6 +565,9 @@
                                 view.setVisibility(VISIBLE);
                             }
                         }).start();
+            } else {
+                // Set alpha to 1f, just in case
+                ViewCompat.setAlpha(mErrorView, 1f);
             }
         } else {
             if (mErrorView.getVisibility() == VISIBLE) {
@@ -584,6 +584,7 @@
                                 }
                             }).start();
                 } else {
+                    mErrorView.setText(error);
                     mErrorView.setVisibility(INVISIBLE);
                 }
             }
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
index e4290ce..44427dc 100755
--- a/design/tests/AndroidManifest.xml
+++ b/design/tests/AndroidManifest.xml
@@ -20,10 +20,13 @@
 
     <uses-sdk
             android:minSdkVersion="7"
+            android:targetSdkVersion="23"
             tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
                       android.support.test.espresso, android.support.test.espresso.idling"/>
 
-    <application android:theme="@style/Theme.AppCompat">
+    <application
+            android:supportsRtl="true"
+            android:theme="@style/Theme.AppCompat">
         <uses-library android:name="android.test.runner"/>
 
         <activity
@@ -32,9 +35,18 @@
 
         <activity
                 android:theme="@style/Theme.AppCompat.NoActionBar"
+                android:name="android.support.design.widget.SnackbarActivity"/>
+
+        <activity
+                android:theme="@style/Theme.AppCompat.NoActionBar"
+                android:name="android.support.design.widget.DynamicCoordinatorLayoutActivity"/>
                 android:name="android.support.design.widget.TabLayoutPoolingActivity"/>
 
         <activity
+            android:theme="@style/Theme.AppCompat.NoActionBar"
+            android:name="android.support.design.widget.TabLayoutPoolingActivity"/>
+
+        <activity
                 android:theme="@style/Theme.AppCompat.NoActionBar"
                 android:name="android.support.design.widget.SnackbarBucketTestsActivity"/>
 
@@ -55,6 +67,10 @@
             android:name="android.support.design.widget.NavigationViewActivity"/>
 
         <activity
+              android:theme="@style/Theme.AppCompat.NoActionBar"
+              android:name="android.support.design.widget.TextInputLayoutActivity"/>
+
+        <activity
                 android:name="android.support.v7.app.AppCompatActivity"/>
 
     </application>
diff --git a/design/tests/res/drawable-nodpi/photo.jpg b/design/tests/res/drawable-nodpi/photo.jpg
new file mode 100644
index 0000000..d5a2ef0
--- /dev/null
+++ b/design/tests/res/drawable-nodpi/photo.jpg
Binary files differ
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml
new file mode 100644
index 0000000..151e213
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:layout_anchor="@+id/app_bar"
+        app:layout_anchorGravity="bottom|center_horizontal"
+        android:src="@drawable/ic_add"
+        android:layout_marginBottom="@dimen/fab_margin"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml
new file mode 100644
index 0000000..bb139d6
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:layout_anchor="@+id/app_bar"
+        app:layout_anchorGravity="bottom|left"
+        android:src="@drawable/ic_add"
+        android:layout_marginLeft="@dimen/fab_margin"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml
new file mode 100644
index 0000000..b67dbd6
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:layout_anchor="@+id/app_bar"
+        app:layout_anchorGravity="bottom|right"
+        android:src="@drawable/ic_add"
+        android:layout_marginRight="@dimen/fab_margin"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml
new file mode 100644
index 0000000..ce20235
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:layout_anchor="@+id/app_bar"
+        app:layout_anchorGravity="bottom|center_horizontal"
+        android:src="@drawable/ic_add"
+        android:layout_marginTop="@dimen/fab_margin"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/v7/appcompat/res/values-xlarge-land/dimens.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml
similarity index 61%
rename from v7/appcompat/res/values-xlarge-land/dimens.xml
rename to design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml
index dea6c74..6c8b4b0 100644
--- a/v7/appcompat/res/values-xlarge-land/dimens.xml
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!--
+     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.
@@ -14,9 +15,11 @@
      limitations under the License.
 -->
 
-<resources>
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-    <!-- Minimum width of the search view text entry area. -->
-    <dimen name="abc_search_view_text_min_width">256dip</dimen>
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
 
-</resources>
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml
new file mode 100644
index 0000000..9c5797b
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:layout_anchor="@+id/app_bar"
+        app:layout_anchorGravity="bottom|right|end"
+        android:src="@drawable/ic_add"
+        android:layout_marginRight="16dp"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml
new file mode 100644
index 0000000..e9c5d0a
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/appbar_height"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_app_bar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_height="?attr/actionBarSize"
+                android:layout_width="match_parent"
+                app:layout_collapseMode="pin"/>
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml
new file mode 100644
index 0000000..f55f470
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/appbar_height"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_app_bar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+            <ImageView
+                android:id="@+id/app_bar_image"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                app:layout_collapseMode="parallax"
+                android:src="@drawable/photo"
+                android:scaleType="centerCrop"/>
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"/>
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml
new file mode 100644
index 0000000..b886561
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_height="?attr/actionBarSize"
+            android:layout_width="match_parent"
+            app:layout_scrollFlags="scroll|enterAlways"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml
new file mode 100644
index 0000000..c13dd7a
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_height="?attr/actionBarSize"
+            android:layout_width="match_parent"
+            app:layout_scrollFlags="scroll|enterAlways"/>
+
+        <android.support.design.widget.TabLayout
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:tabMode="scrollable"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml
new file mode 100644
index 0000000..ffcfd6d
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_height="?attr/actionBarSize"
+            android:layout_width="match_parent"
+            app:layout_scrollFlags="scroll|enterAlways"/>
+
+        <android.support.design.widget.TabLayout
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_scrollFlags="scroll|enterAlways"
+            app:tabMode="scrollable"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml
new file mode 100644
index 0000000..27272f2
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_height="?attr/actionBarSize"
+            android:layout_width="match_parent"
+            app:layout_scrollFlags="scroll|enterAlways|snap"/>
+
+        <android.support.design.widget.TabLayout
+            android:id="@+id/tabs"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_scrollFlags="scroll|enterAlways|snap"
+            app:tabMode="scrollable"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml b/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml
new file mode 100644
index 0000000..1a8102f
--- /dev/null
+++ b/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/appbar_height"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_app_bar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_height="?attr/actionBarSize"
+                android:layout_width="match_parent"
+                app:layout_collapseMode="pin"/>
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview" />
+</merge>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_behavior_annotation.xml b/design/tests/res/layout/design_snackbar_behavior_annotation.xml
new file mode 100644
index 0000000..9529770
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_annotation.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined as an annotation on custom class. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.custom.CustomTextView
+        android:id="@+id/text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:text="text1"
+        android:textSize="18dip"
+        android:textStyle="bold"
+        android:padding="12dip"
+        app:textAllCaps="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml b/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml
new file mode 100644
index 0000000..88928bb
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined as a child attribute in XML. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.v7.widget.AppCompatTextView
+        android:id="@+id/text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:text="text1"
+        android:textSize="18dip"
+        android:textStyle="bold"
+        android:padding="12dip"
+        app:textAllCaps="true"
+        app:layout_behavior="android.support.design.custom.TestFloatingBehavior" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_behavior_runtime.xml b/design/tests/res/layout/design_snackbar_behavior_runtime.xml
new file mode 100644
index 0000000..85415ea
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_runtime.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined via a runtime API call. -->
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.v7.widget.AppCompatTextView
+        android:id="@+id/text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|start"
+        android:text="text1"
+        android:textSize="18dip"
+        android:textStyle="bold"
+        android:padding="12dip"
+        app:textAllCaps="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_with_fab.xml b/design/tests/res/layout/design_snackbar_with_fab.xml
new file mode 100644
index 0000000..a06dc1b
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_with_fab.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        android:src="@drawable/ic_add"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-land/config.xml b/design/tests/res/layout/design_tabs.xml
similarity index 70%
rename from v7/appcompat/res/values-land/config.xml
rename to design/tests/res/layout/design_tabs.xml
index d0d990d..fbb24bd 100644
--- a/v7/appcompat/res/values-land/config.xml
+++ b/design/tests/res/layout/design_tabs.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+     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.
@@ -13,6 +14,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+
+<android.support.design.widget.TabLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" />
diff --git a/design/tests/res/layout/design_tabs_items.xml b/design/tests/res/layout/design_tabs_items.xml
index 255acf3..e67f3b6 100644
--- a/design/tests/res/layout/design_tabs_items.xml
+++ b/design/tests/res/layout/design_tabs_items.xml
@@ -17,8 +17,8 @@
 
 <android.support.design.widget.TabLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent">
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
 
     <android.support.design.widget.TabItem
             android:text="@string/tab_layout_text"/>
diff --git a/design/tests/res/layout/design_tabs_twice.xml b/design/tests/res/layout/design_tabs_twice.xml
index aff1262..1421c10 100644
--- a/design/tests/res/layout/design_tabs_twice.xml
+++ b/design/tests/res/layout/design_tabs_twice.xml
@@ -15,17 +15,17 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:app="http://schemas.android.com/apk/res-auto"
-              android:id="@+id/container"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
     <android.support.design.widget.TabLayout
         android:id="@+id/tabs_1"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent">
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
 
         <android.support.design.widget.TabItem
             android:text="@string/tab_layout_text"/>
@@ -40,7 +40,7 @@
 
     <android.support.design.widget.TabLayout
         android:id="@+id/tabs_2"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"/>
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
 
 </LinearLayout>
diff --git a/design/tests/res/layout/design_tabs_viewpager.xml b/design/tests/res/layout/design_tabs_viewpager.xml
index f573e4b..877c690 100644
--- a/design/tests/res/layout/design_tabs_viewpager.xml
+++ b/design/tests/res/layout/design_tabs_viewpager.xml
@@ -15,28 +15,29 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:app="http://schemas.android.com/apk/res-auto"
-              android:id="@+id/container"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
 
     <android.support.v7.widget.Toolbar
-            android:id="@+id/toolbar"
-            android:layout_height="?attr/actionBarSize"
-            android:layout_width="match_parent"
-            android:background="?attr/colorPrimary"
-            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
-            app:contentInsetStart="72dp"
-            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        app:contentInsetStart="72dp"
+        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
 
     <include layout="@layout/tab_layout_unbound" />
 
     <android.support.v4.view.ViewPager
-            android:id="@+id/tabs_viewpager"
-            android:layout_height="0px"
-            android:layout_width="match_parent"
-            android:layout_weight="1"/>
+        android:id="@+id/tabs_viewpager"
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"/>
 
 </LinearLayout>
diff --git a/design/tests/res/layout/design_tabs_with_non_tabitems.xml b/design/tests/res/layout/design_tabs_with_non_tabitems.xml
index a93a8a2..cb96715 100644
--- a/design/tests/res/layout/design_tabs_with_non_tabitems.xml
+++ b/design/tests/res/layout/design_tabs_with_non_tabitems.xml
@@ -16,17 +16,17 @@
 -->
 
 <android.support.design.widget.TabLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
     <TextView
-            android:text="@string/tab_layout_text"/>
+        android:text="@string/tab_layout_text"/>
 
     <TextView
-            android:src="@android:drawable/star_on"/>
+        android:src="@android:drawable/star_on"/>
 
     <include
-            layout="@layout/design_tab_item_custom"/>
+        layout="@layout/design_tab_item_custom"/>
 
 </android.support.design.widget.TabLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_text_input.xml b/design/tests/res/layout/design_text_input.xml
new file mode 100644
index 0000000..1312b50
--- /dev/null
+++ b/design/tests/res/layout/design_text_input.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:app="http://schemas.android.com/apk/res-auto"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical"
+              android:padding="16dp">
+
+    <android.support.design.widget.TextInputLayout
+            android:id="@+id/textinput"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:errorEnabled="true">
+
+        <android.support.design.widget.TextInputEditText
+                android:id="@+id/textinput_edittext"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/textinput_hint"/>
+
+    </android.support.design.widget.TextInputLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/dynamic_coordinator_layout.xml b/design/tests/res/layout/dynamic_coordinator_layout.xml
new file mode 100644
index 0000000..a2253c7
--- /dev/null
+++ b/design/tests/res/layout/dynamic_coordinator_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- ViewStub that will be inflated to a CoordinatorLayout at test runtime. -->
+<ViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/coordinator_stub"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:inflatedId="@+id/coordinator_layout"
+    android:fitsSystemWindows="true" />
diff --git a/design/tests/res/layout/include_appbar_scrollview.xml b/design/tests/res/layout/include_appbar_scrollview.xml
new file mode 100644
index 0000000..22a7276
--- /dev/null
+++ b/design/tests/res/layout/include_appbar_scrollview.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<android.support.v4.widget.NestedScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="1000dp"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:text="@string/scroll_top" />
+
+        <TextView
+            android:id="@+id/textview_dialogue"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:padding="16dp"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:text="@string/scroll_bottom" />
+
+    </LinearLayout>
+
+</android.support.v4.widget.NestedScrollView>
\ No newline at end of file
diff --git a/design/tests/res/values/dimens.xml b/design/tests/res/values/dimens.xml
index d49a285..299fc8b 100644
--- a/design/tests/res/values/dimens.xml
+++ b/design/tests/res/values/dimens.xml
@@ -25,4 +25,7 @@
     <dimen name="drawable_small_size">12dip</dimen>
     <dimen name="drawable_medium_size">16dip</dimen>
     <dimen name="drawable_large_size">20dip</dimen>
+
+    <dimen name="appbar_height">128dip</dimen>
+    <dimen name="fab_margin">32dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/design/tests/res/values/strings.xml b/design/tests/res/values/strings.xml
index 05dd55e..a431e2b 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -20,4 +20,26 @@
     <string name="navigate_profile">Profile</string>
     <string name="navigate_people">People</string>
     <string name="navigate_settings">Settings</string>
+
+    <string name="snackbar_text">This is a test message</string>
+    <string name="snackbar_action">Undo</string>
+
+    <string name="text1">Text</string>
+
+    <string name="scroll_top">Top</string>
+    <string name="scroll_bottom">Bottom</string>
+    <string name="design_appbar_collapsing_toolbar_pin">AppBar/Collapsing Toolbar (pinned)</string>
+    <string name="design_appbar_collapsing_toolbar_scroll">AppBar/Collapsing Toolbar (scroll off)</string>
+    <string name="design_appbar_collapsing_toolbar_pin_fab">AppBar/Collapsing Toolbar (pinned with FAB)</string>
+    <string name="design_appbar_toolbar_scroll_tabs_scroll">AppBar/Toolbar Scroll + Tabs Scroll</string>
+    <string name="design_appbar_toolbar_scroll_tabs_scroll_snap">AppBar/Toolbar Scroll + Tabs Scroll + Snap</string>
+    <string name="design_appbar_toolbar_scroll_tabs_pin">AppBar/Toolbar Scroll + Tabs Pin</string>
+    <string name="design_appbar_collapsing_toolbar_with_image">AppBar/Collapsing Toolbar + Parallax Image</string>
+    <string name="design_appbar_anchored_fab_margin_bottom">AppBar + anchored FAB with bottom margin</string>
+    <string name="design_appbar_anchored_fab_margin_top">AppBar + anchored FAB with top margin</string>
+    <string name="design_appbar_anchored_fab_margin_left">AppBar + anchored FAB with left margin</string>
+    <string name="design_appbar_anchored_fab_margin_right">AppBar + anchored FAB with right margin</string>
+
+    <string name="textinput_hint">Hint to the user</string>
+
 </resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/custom/CustomTextView.java b/design/tests/src/android/support/design/custom/CustomTextView.java
new file mode 100644
index 0000000..3b3025e
--- /dev/null
+++ b/design/tests/src/android/support/design/custom/CustomTextView.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.design.custom;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+
+@CoordinatorLayout.DefaultBehavior(TestFloatingBehavior.class)
+public class CustomTextView extends AppCompatTextView {
+    public CustomTextView(Context context) {
+        super(context);
+    }
+
+    public CustomTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+}
diff --git a/design/tests/src/android/support/design/custom/TestFloatingBehavior.java b/design/tests/src/android/support/design/custom/TestFloatingBehavior.java
new file mode 100644
index 0000000..a508736
--- /dev/null
+++ b/design/tests/src/android/support/design/custom/TestFloatingBehavior.java
@@ -0,0 +1,49 @@
+/*
+ * 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.design.custom;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.Snackbar;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+public class TestFloatingBehavior extends CoordinatorLayout.Behavior<TextView> {
+    // Default constructor is needed to instantiate a Behavior object when it is attached
+    // to custom view class as class-level annotation
+    public TestFloatingBehavior() {
+    }
+
+    // This constructor is needed to instantiate a Behavior object when it is attached to a
+    // view via layout_behavior XML attribute
+    public TestFloatingBehavior(Context context, AttributeSet attrs) {
+    }
+
+    @Override
+    public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
+        return dependency instanceof Snackbar.SnackbarLayout;
+    }
+
+    @Override
+    public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
+            View dependency) {
+        ViewCompat.setTranslationY(child, Math.min(0,
+                ViewCompat.getTranslationY(dependency) - dependency.getHeight()));
+        return true;
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/Cheeses.java b/design/tests/src/android/support/design/testutils/Cheeses.java
new file mode 100644
index 0000000..6215569
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * 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.design.testutils;
+
+public class Cheeses {
+
+    public static final String[] sCheeseStrings = {
+            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+            "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+            "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+            "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+            "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+            "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+            "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+            "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+            "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+            "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+            "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+            "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+            "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+            "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+            "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+            "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+            "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+            "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+            "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+            "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+            "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+            "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+            "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+            "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+            "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+            "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+            "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+            "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+            "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+            "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+            "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+            "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+            "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+            "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+            "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+            "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+            "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+            "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+            "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+            "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+            "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+            "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+            "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+            "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+            "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+            "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+            "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+            "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+            "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+            "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+            "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+            "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+            "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+            "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+            "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+            "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+            "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+            "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+            "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+            "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+            "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+            "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+            "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+            "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+            "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+            "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+            "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+            "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+            "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+            "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+            "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+            "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+            "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+            "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+            "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+            "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+            "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+            "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+            "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+            "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+            "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+            "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+            "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+            "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+            "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+            "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+            "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+            "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+            "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+            "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+            "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+            "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+            "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+            "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+            "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+            "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+            "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+            "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+            "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+            "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+            "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+            "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+            "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+            "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+            "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+            "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+            "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+            "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+            "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+            "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+            "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+            "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+            "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+            "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+            "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+            "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+            "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+            "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+            "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+            "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+            "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+            "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+    };
+
+}
diff --git a/design/tests/src/android/support/design/testutils/Shakespeare.java b/design/tests/src/android/support/design/testutils/Shakespeare.java
new file mode 100644
index 0000000..285a185
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/Shakespeare.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.design.testutils;
+
+public final class Shakespeare {
+    /**
+     * Our data, part 1.
+     */
+    public static final String[] TITLES = {
+            "Henry IV (1)",
+            "Henry V",
+            "Henry VIII",
+            "Richard II",
+            "Richard III",
+            "Merchant of Venice",
+            "Othello",
+            "King Lear"
+    };
+
+    /**
+     * Our data, part 2.
+     */
+    public static final String[] DIALOGUE = {
+            "So shaken as we are, so wan with care," +
+            "Find we a time for frighted peace to pant," +
+            "And breathe short-winded accents of new broils" +
+            "To be commenced in strands afar remote." +
+            "No more the thirsty entrance of this soil" +
+            "Shall daub her lips with her own children's blood;" +
+            "Nor more shall trenching war channel her fields," +
+            "Nor bruise her flowerets with the armed hoofs" +
+            "Of hostile paces: those opposed eyes," +
+            "Which, like the meteors of a troubled heaven," +
+            "All of one nature, of one substance bred," +
+            "Did lately meet in the intestine shock" +
+            "And furious close of civil butchery" +
+            "Shall now, in mutual well-beseeming ranks," +
+            "March all one way and be no more opposed" +
+            "Against acquaintance, kindred and allies:" +
+            "The edge of war, like an ill-sheathed knife," +
+            "No more shall cut his master. Therefore, friends," +
+            "As far as to the sepulchre of Christ," +
+            "Whose soldier now, under whose blessed cross" +
+            "We are impressed and engaged to fight," +
+            "Forthwith a power of English shall we levy;" +
+            "Whose arms were moulded in their mothers' womb" +
+            "To chase these pagans in those holy fields" +
+            "Over whose acres walk'd those blessed feet" +
+            "Which fourteen hundred years ago were nail'd" +
+            "For our advantage on the bitter cross." +
+            "But this our purpose now is twelve month old," +
+            "And bootless 'tis to tell you we will go:" +
+            "Therefore we meet not now. Then let me hear" +
+            "Of you, my gentle cousin Westmoreland," +
+            "What yesternight our council did decree" +
+            "In forwarding this dear expedience.",
+
+            "Hear him but reason in divinity," +
+            "And all-admiring with an inward wish" +
+            "You would desire the king were made a prelate:" +
+            "Hear him debate of commonwealth affairs," +
+            "You would say it hath been all in all his study:" +
+            "List his discourse of war, and you shall hear" +
+            "A fearful battle render'd you in music:" +
+            "Turn him to any cause of policy," +
+            "The Gordian knot of it he will unloose," +
+            "Familiar as his garter: that, when he speaks," +
+            "The air, a charter'd libertine, is still," +
+            "And the mute wonder lurketh in men's ears," +
+            "To steal his sweet and honey'd sentences;" +
+            "So that the art and practic part of life" +
+            "Must be the mistress to this theoric:" +
+            "Which is a wonder how his grace should glean it," +
+            "Since his addiction was to courses vain," +
+            "His companies unletter'd, rude and shallow," +
+            "His hours fill'd up with riots, banquets, sports," +
+            "And never noted in him any study," +
+            "Any retirement, any sequestration" +
+            "From open haunts and popularity.",
+
+            "I come no more to make you laugh: things now," +
+            "That bear a weighty and a serious brow," +
+            "Sad, high, and working, full of state and woe," +
+            "Such noble scenes as draw the eye to flow," +
+            "We now present. Those that can pity, here" +
+            "May, if they think it well, let fall a tear;" +
+            "The subject will deserve it. Such as give" +
+            "Their money out of hope they may believe," +
+            "May here find truth too. Those that come to see" +
+            "Only a show or two, and so agree" +
+            "The play may pass, if they be still and willing," +
+            "I'll undertake may see away their shilling" +
+            "Richly in two short hours. Only they" +
+            "That come to hear a merry bawdy play," +
+            "A noise of targets, or to see a fellow" +
+            "In a long motley coat guarded with yellow," +
+            "Will be deceived; for, gentle hearers, know," +
+            "To rank our chosen truth with such a show" +
+            "As fool and fight is, beside forfeiting" +
+            "Our own brains, and the opinion that we bring," +
+            "To make that only true we now intend," +
+            "Will leave us never an understanding friend." +
+            "Therefore, for goodness' sake, and as you are known" +
+            "The first and happiest hearers of the town," +
+            "Be sad, as we would make ye: think ye see" +
+            "The very persons of our noble story" +
+            "As they were living; think you see them great," +
+            "And follow'd with the general throng and sweat" +
+            "Of thousand friends; then in a moment, see" +
+            "How soon this mightiness meets misery:" +
+            "And, if you can be merry then, I'll say" +
+            "A man may weep upon his wedding-day.",
+
+            "First, heaven be the record to my speech!" +
+            "In the devotion of a subject's love," +
+            "Tendering the precious safety of my prince," +
+            "And free from other misbegotten hate," +
+            "Come I appellant to this princely presence." +
+            "Now, Thomas Mowbray, do I turn to thee," +
+            "And mark my greeting well; for what I speak" +
+            "My body shall make good upon this earth," +
+            "Or my divine soul answer it in heaven." +
+            "Thou art a traitor and a miscreant," +
+            "Too good to be so and too bad to live," +
+            "Since the more fair and crystal is the sky," +
+            "The uglier seem the clouds that in it fly." +
+            "Once more, the more to aggravate the note," +
+            "With a foul traitor's name stuff I thy throat;" +
+            "And wish, so please my sovereign, ere I move," +
+            "What my tongue speaks my right drawn sword may prove.",
+
+            "Now is the winter of our discontent" +
+            "Made glorious summer by this sun of York;" +
+            "And all the clouds that lour'd upon our house" +
+            "In the deep bosom of the ocean buried." +
+            "Now are our brows bound with victorious wreaths;" +
+            "Our bruised arms hung up for monuments;" +
+            "Our stern alarums changed to merry meetings," +
+            "Our dreadful marches to delightful measures." +
+            "Grim-visaged war hath smooth'd his wrinkled front;" +
+            "And now, instead of mounting barded steeds" +
+            "To fright the souls of fearful adversaries," +
+            "He capers nimbly in a lady's chamber" +
+            "To the lascivious pleasing of a lute." +
+            "But I, that am not shaped for sportive tricks," +
+            "Nor made to court an amorous looking-glass;" +
+            "I, that am rudely stamp'd, and want love's majesty" +
+            "To strut before a wanton ambling nymph;" +
+            "I, that am curtail'd of this fair proportion," +
+            "Cheated of feature by dissembling nature," +
+            "Deformed, unfinish'd, sent before my time" +
+            "Into this breathing world, scarce half made up," +
+            "And that so lamely and unfashionable" +
+            "That dogs bark at me as I halt by them;" +
+            "Why, I, in this weak piping time of peace," +
+            "Have no delight to pass away the time," +
+            "Unless to spy my shadow in the sun" +
+            "And descant on mine own deformity:" +
+            "And therefore, since I cannot prove a lover," +
+            "To entertain these fair well-spoken days," +
+            "I am determined to prove a villain" +
+            "And hate the idle pleasures of these days." +
+            "Plots have I laid, inductions dangerous," +
+            "By drunken prophecies, libels and dreams," +
+            "To set my brother Clarence and the king" +
+            "In deadly hate the one against the other:" +
+            "And if King Edward be as true and just" +
+            "As I am subtle, false and treacherous," +
+            "This day should Clarence closely be mew'd up," +
+            "About a prophecy, which says that 'G'" +
+            "Of Edward's heirs the murderer shall be." +
+            "Dive, thoughts, down to my soul: here" +
+            "Clarence comes.",
+
+            "To bait fish withal: if it will feed nothing else," +
+            "it will feed my revenge. He hath disgraced me, and" +
+            "hindered me half a million; laughed at my losses," +
+            "mocked at my gains, scorned my nation, thwarted my" +
+            "bargains, cooled my friends, heated mine" +
+            "enemies; and what's his reason? I am a Jew. Hath" +
+            "not a Jew eyes? hath not a Jew hands, organs," +
+            "dimensions, senses, affections, passions? fed with" +
+            "the same food, hurt with the same weapons, subject" +
+            "to the same diseases, healed by the same means," +
+            "warmed and cooled by the same winter and summer, as" +
+            "a Christian is? If you prick us, do we not bleed?" +
+            "if you tickle us, do we not laugh? if you poison" +
+            "us, do we not die? and if you wrong us, shall we not" +
+            "revenge? If we are like you in the rest, we will" +
+            "resemble you in that. If a Jew wrong a Christian," +
+            "what is his humility? Revenge. If a Christian" +
+            "wrong a Jew, what should his sufferance be by" +
+            "Christian example? Why, revenge. The villany you" +
+            "teach me, I will execute, and it shall go hard but I" +
+            "will better the instruction.",
+
+            "Virtue! a fig! 'tis in ourselves that we are thus" +
+            "or thus. Our bodies are our gardens, to the which" +
+            "our wills are gardeners: so that if we will plant" +
+            "nettles, or sow lettuce, set hyssop and weed up" +
+            "thyme, supply it with one gender of herbs, or" +
+            "distract it with many, either to have it sterile" +
+            "with idleness, or manured with industry, why, the" +
+            "power and corrigible authority of this lies in our" +
+            "wills. If the balance of our lives had not one" +
+            "scale of reason to poise another of sensuality, the" +
+            "blood and baseness of our natures would conduct us" +
+            "to most preposterous conclusions: but we have" +
+            "reason to cool our raging motions, our carnal" +
+            "stings, our unbitted lusts, whereof I take this that" +
+            "you call love to be a sect or scion.",
+
+            "Blow, winds, and crack your cheeks! rage! blow!" +
+            "You cataracts and hurricanoes, spout" +
+            "Till you have drench'd our steeples, drown'd the cocks!" +
+            "You sulphurous and thought-executing fires," +
+            "Vaunt-couriers to oak-cleaving thunderbolts," +
+            "Singe my white head! And thou, all-shaking thunder," +
+            "Smite flat the thick rotundity o' the world!" +
+            "Crack nature's moulds, an germens spill at once," +
+            "That make ingrateful man!"
+    };
+}
diff --git a/design/tests/src/android/support/design/testutils/SnackbarUtils.java b/design/tests/src/android/support/design/testutils/SnackbarUtils.java
new file mode 100644
index 0000000..67a30dd
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/SnackbarUtils.java
@@ -0,0 +1,161 @@
+/*
+ * 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.design.testutils;
+
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.design.testutils.TestUtilsActions.waitUntilIdle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+
+public class SnackbarUtils {
+    private static class SnackbarShownCallback extends Snackbar.Callback
+            implements IdlingResource {
+        private boolean mIsShown = false;
+
+        @Nullable
+        private IdlingResource.ResourceCallback mCallback;
+
+        private boolean mNeedsIdle = false;
+
+        @Override
+        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+            mCallback = resourceCallback;
+        }
+
+        @Override
+        public String getName() {
+            return "Snackbar shown callback";
+        }
+
+        @Override
+        public boolean isIdleNow() {
+            if (!mNeedsIdle) {
+                return true;
+            } else {
+                return mIsShown;
+            }
+        }
+
+        @Override
+        public void onShown(Snackbar snackbar) {
+            mIsShown = true;
+            if (mCallback != null) {
+                mCallback.onTransitionToIdle();
+            }
+        }
+    }
+
+    private static class SnackbarDismissedCallback extends Snackbar.Callback
+            implements IdlingResource {
+        private boolean mIsDismissed = false;
+
+        @Nullable
+        private IdlingResource.ResourceCallback mCallback;
+
+        private boolean mNeedsIdle = false;
+
+        @Override
+        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+            mCallback = resourceCallback;
+        }
+
+        @Override
+        public String getName() {
+            return "Snackbar dismissed callback";
+        }
+
+        @Override
+        public boolean isIdleNow() {
+            if (!mNeedsIdle) {
+                return true;
+            } else {
+                return mIsDismissed;
+            }
+        }
+
+        @Override
+        public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
+            mIsDismissed = true;
+            if (mCallback != null) {
+                mCallback.onTransitionToIdle();
+            }
+        }
+    }
+
+    /**
+     * Helper method that shows that specified {@link Snackbar} and waits until
+     * it has been fully shown. Note that calling this method will reset the currently
+     * set {@link Snackbar.Callback}.
+     */
+    public static void showSnackbarAndWaitUntilFullyShown(Snackbar snackbar) {
+        SnackbarShownCallback snackbarCallback = new SnackbarShownCallback();
+        snackbar.setCallback(snackbarCallback);
+        try {
+            // Register our listener as idling resource so that Espresso waits until the
+            // the snackbar has been fully shown
+            Espresso.registerIdlingResources(snackbarCallback);
+            // Show the snackbar
+            snackbar.show();
+            // Mark the callback to require waiting for idle state
+            snackbarCallback.mNeedsIdle = true;
+            // Perform a dummy Espresso action that loops until the UI thread is idle. This
+            // effectively blocks us until the Snackbar has completed its sliding animation.
+            onView(isRoot()).perform(waitUntilIdle());
+            snackbarCallback.mNeedsIdle = false;
+        } finally {
+            // Unregister our idling resource
+            Espresso.unregisterIdlingResources(snackbarCallback);
+            // And remove our tracker listener from Snackbar
+            snackbar.setCallback(null);
+        }
+    }
+
+    /**
+     * Helper method that dismissed that specified {@link Snackbar} and waits until
+     * it has been fully dismissed. Note that calling this method will reset the currently
+     * set {@link Snackbar.Callback}.
+     */
+    public static void dismissSnackbarAndWaitUntilFullyDismissed(Snackbar snackbar) {
+        SnackbarDismissedCallback snackbarCallback = new SnackbarDismissedCallback();
+        snackbar.setCallback(snackbarCallback);
+        try {
+            // Register our listener as idling resource so that Espresso waits until the
+            // the snackbar has been fully dismissed
+            Espresso.registerIdlingResources(snackbarCallback);
+            // Dismiss the snackbar
+            snackbar.dismiss();
+            // Mark the callback to require waiting for idle state
+            snackbarCallback.mNeedsIdle = true;
+            // Perform a dummy Espresso action that loops until the UI thread is idle. This
+            // effectively blocks us until the Snackbar has completed its sliding animation.
+            onView(isRoot()).perform(waitUntilIdle());
+            snackbarCallback.mNeedsIdle = false;
+        } finally {
+            // Unregister our idling resource
+            Espresso.unregisterIdlingResources(snackbarCallback);
+            // And remove our tracker listener from Snackbar
+            snackbar.setCallback(null);
+        }
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/TabLayoutActions.java b/design/tests/src/android/support/design/testutils/TabLayoutActions.java
index f659fa5..149b14f 100644
--- a/design/tests/src/android/support/design/testutils/TabLayoutActions.java
+++ b/design/tests/src/android/support/design/testutils/TabLayoutActions.java
@@ -63,6 +63,34 @@
     }
 
     /**
+     * Wires <code>TabLayout</code> to <code>ViewPager</code> content.
+     */
+    public static ViewAction setupWithViewPager(final @Nullable ViewPager viewPager,
+            final boolean autoRefresh) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayingAtLeast(90);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Setup with ViewPager content";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TabLayout tabLayout = (TabLayout) view;
+                tabLayout.setupWithViewPager(viewPager, autoRefresh);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
      * Selects the specified tab in the <code>TabLayout</code>.
      */
     public static ViewAction selectTab(final int tabIndex) {
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsActions.java b/design/tests/src/android/support/design/testutils/TestUtilsActions.java
index 320f6e7..0ad7c88 100644
--- a/design/tests/src/android/support/design/testutils/TestUtilsActions.java
+++ b/design/tests/src/android/support/design/testutils/TestUtilsActions.java
@@ -18,20 +18,25 @@
 
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.design.widget.CollapsingToolbarLayout;
 import android.support.design.widget.TabLayout;
 import android.support.test.espresso.UiController;
 import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
 import android.support.v4.view.ViewPager;
+import android.support.v7.widget.Toolbar;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 import org.hamcrest.Matcher;
 
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
 
 public class TestUtilsActions {
     /**
-     * Replaces an existing <code>TabLayout</code> with a new one inflated from the specified
+     * Replaces an existing {@link TabLayout} with a new one inflated from the specified
      * layout resource.
      */
     public static ViewAction replaceTabLayout(final @LayoutRes int tabLayoutResId) {
@@ -73,4 +78,137 @@
             }
         };
     }
+
+    /**
+     * Sets layout direction on the view.
+     */
+    public static ViewAction setLayoutDirection(final int layoutDirection) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "set layout direction";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                ViewCompat.setLayoutDirection(view, layoutDirection);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets title on the {@link CollapsingToolbarLayout}.
+     */
+    public static ViewAction setTitle(final CharSequence title) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(CollapsingToolbarLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "set toolbar title";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                CollapsingToolbarLayout collapsingToolbarLayout =
+                        (CollapsingToolbarLayout) view;
+                collapsingToolbarLayout.setTitle(title);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Sets text content on {@link TextView}
+     */
+    public static ViewAction setText(final @Nullable CharSequence text) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextView.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set text";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                textView.setText(text);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Adds tabs to {@link TabLayout}
+     */
+    public static ViewAction addTabs(final String... tabs) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TabLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "TabLayout add tabs";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TabLayout tabLayout = (TabLayout) view;
+                for (int i = 0; i < tabs.length; i++) {
+                    tabLayout.addTab(tabLayout.newTab().setText(tabs[i]));
+                }
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
+     * Dummy Espresso action that waits until the UI thread is idle. This action can be performed
+     * on the root view to wait for an ongoing animation to be completed.
+     */
+    public static ViewAction waitUntilIdle() {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isRoot();
+            }
+
+            @Override
+            public String getDescription() {
+                return "wait for idle";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
 }
diff --git a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
new file mode 100755
index 0000000..3360029
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
@@ -0,0 +1,78 @@
+/*
+ * 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.design.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.support.design.widget.TextInputLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class TextInputLayoutActions {
+
+    public static ViewAction setErrorEnabled(final boolean enabled) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextInputLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Enables/disables the error";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextInputLayout layout = (TextInputLayout) view;
+                layout.setErrorEnabled(enabled);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    public static ViewAction setError(final CharSequence error) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(TextInputLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Sets the error";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextInputLayout layout = (TextInputLayout) view;
+                layout.setError(error);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/ViewPagerActions.java b/design/tests/src/android/support/design/testutils/ViewPagerActions.java
index 7ab1560..e7b4bf7 100644
--- a/design/tests/src/android/support/design/testutils/ViewPagerActions.java
+++ b/design/tests/src/android/support/design/testutils/ViewPagerActions.java
@@ -162,7 +162,7 @@
 
             @Override
             public String getDescription() {
-                return "ViewPager move one page to the right";
+                return "ViewPager move to a specific page";
             }
 
             @Override
diff --git a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
new file mode 100644
index 0000000..793f6d9
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.design.widget;
+
+import android.support.annotation.CallSuper;
+import android.support.annotation.IdRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.Shakespeare;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import static android.support.design.testutils.TestUtilsActions.setText;
+import static android.support.design.testutils.TestUtilsActions.setTitle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+public abstract class AppBarLayoutBaseTest extends BaseDynamicCoordinatorLayoutTest {
+
+    protected AppBarLayout mAppBar;
+
+    protected CollapsingToolbarLayout mCollapsingToolbar;
+
+    protected Toolbar mToolbar;
+
+    protected TextView mTextView;
+
+    protected static void performVerticalSwipeUpGesture(@IdRes int containerId, final int swipeX,
+            final int swipeStartY, final int swipeAmountY) {
+        onView(withId(containerId)).perform(new GeneralSwipeAction(
+                Swipe.SLOW,
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY };
+                    }
+                },
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY - swipeAmountY };
+                    }
+                }, Press.FINGER));
+    }
+
+    protected static void performVerticalSwipeDownGesture(@IdRes int containerId, final int swipeX,
+            final int swipeStartY, final int swipeAmountY) {
+        onView(withId(containerId)).perform(new GeneralSwipeAction(
+                Swipe.SLOW,
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY };
+                    }
+                },
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY + swipeAmountY };
+                    }
+                }, Press.FINGER));
+    }
+
+    @CallSuper
+    protected void configureContent(final @LayoutRes int layoutResId,
+            final @StringRes int titleResId) {
+        onView(withId(R.id.coordinator_stub)).perform(inflateViewStub(layoutResId));
+
+        mAppBar = (AppBarLayout) mCoordinatorLayout.findViewById(R.id.app_bar);
+        mCollapsingToolbar =
+                (CollapsingToolbarLayout) mAppBar.findViewById(R.id.collapsing_app_bar);
+        mToolbar = (Toolbar) mAppBar.findViewById(R.id.toolbar);
+
+        final AppCompatActivity activity = mActivityTestRule.getActivity();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.setSupportActionBar(mToolbar);
+            }
+        });
+
+        final CharSequence activityTitle = activity.getString(titleResId);
+        activity.setTitle(activityTitle);
+        if (mCollapsingToolbar != null) {
+            onView(withId(R.id.collapsing_app_bar)).perform(setTitle(activityTitle));
+        }
+
+        TextView dialog = (TextView) mCoordinatorLayout.findViewById(R.id.textview_dialogue);
+        if (dialog != null) {
+            onView(withId(R.id.textview_dialogue)).perform(
+                    setText(TextUtils.concat(Shakespeare.DIALOGUE)));
+        }
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java b/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java
new file mode 100644
index 0000000..748e079
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.design.widget;
+
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class AppBarWithAnchoredFabMarginsTest extends AppBarLayoutBaseTest {
+    private int mFabMargin;
+
+    @Before
+    public void setup() {
+        mFabMargin = mActivityTestRule.getActivity().getResources().getDimensionPixelSize(
+                R.dimen.fab_margin);
+    }
+
+    @Test
+    public void testFabBottomMargin() throws Throwable {
+        configureContent(R.layout.design_appbar_anchored_fab_margin_bottom,
+                R.string.design_appbar_anchored_fab_margin_bottom);
+
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        final CoordinatorLayout.LayoutParams fabLp =
+                (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+        assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] fabOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        fab.getLocationOnScreen(fabOnScreenXY);
+
+        // FAB is horizontally centered in the coordinate system of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() / 2,
+                fabOnScreenXY[0] + fab.getWidth() / 2, 1);
+        // Bottom margin is in the coordinate space of the parent (CoordinatorLayout) and not
+        // the anchor. Since our FAB is far enough from the bottom edge of CoordinatorLayout,
+        // we are expecting the vertical center of the FAB to be aligned with the bottom edge
+        // of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+                fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+    }
+
+    @Test
+    public void testFabTopMargin() throws Throwable {
+        configureContent(R.layout.design_appbar_anchored_fab_margin_top,
+                R.string.design_appbar_anchored_fab_margin_top);
+
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        final CoordinatorLayout.LayoutParams fabLp =
+                (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+        assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] fabOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        fab.getLocationOnScreen(fabOnScreenXY);
+
+        // FAB is horizontally centered in the coordinate system of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() / 2,
+                fabOnScreenXY[0] + fab.getWidth() / 2, 1);
+        // Top margin is in the coordinate space of the parent (CoordinatorLayout) and not
+        // the anchor. Since our FAB is far enough from the bottom edge of CoordinatorLayout,
+        // we are expecting the vertical center of the FAB to be aligned with the bottom edge
+        // of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+                fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+    }
+
+    @Test
+    public void testFabLeftMargin() throws Throwable {
+        configureContent(R.layout.design_appbar_anchored_fab_margin_left,
+                R.string.design_appbar_anchored_fab_margin_left);
+
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        final CoordinatorLayout.LayoutParams fabLp =
+                (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+        assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] fabOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        fab.getLocationOnScreen(fabOnScreenXY);
+
+        // FAB is left-aligned in the coordinate system of its anchor (app bar). In addition,
+        // its left margin "pushes" it away in the coordinate system of the parent
+        // (CoordinatorLayout)
+        assertEquals(appbarOnScreenXY[0] + mFabMargin, fabOnScreenXY[0], 1);
+        // FAB's vertical center should be aligned with the bottom edge of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+                fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+    }
+
+    @Test
+    public void testFabRightMargin() throws Throwable {
+        configureContent(R.layout.design_appbar_anchored_fab_margin_right,
+                R.string.design_appbar_anchored_fab_margin_right);
+
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        final CoordinatorLayout.LayoutParams fabLp =
+                (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+        assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] fabOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        fab.getLocationOnScreen(fabOnScreenXY);
+
+        // FAB is right-aligned in the coordinate system of its anchor (app bar). In addition,
+        // its right margin "pushes" it away in the coordinate system of the parent
+        // (CoordinatorLayout)
+        assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() - mFabMargin,
+                fabOnScreenXY[0] + fab.getWidth(), 1);
+        // FAB's vertical center should be aligned with the bottom edge of its anchor (app bar).
+        assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+                fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
new file mode 100644
index 0000000..737668b
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -0,0 +1,352 @@
+/*
+ * 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.design.widget;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ImageView;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+public class AppBarWithCollapsingToolbarTest extends AppBarLayoutBaseTest {
+    @Test
+    public void testPinnedToolbar() {
+        configureContent(R.layout.design_appbar_toolbar_collapse_pin,
+                R.string.design_appbar_collapsing_toolbar_pin);
+
+        CollapsingToolbarLayout.LayoutParams toolbarLp =
+                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+                toolbarLp.getCollapseMode());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+        final int shortSwipeAmount = toolbarHeight;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be visually snapped below the system status bar.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-up gesture
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                shortSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be visually snapped below the system status bar
+        // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-down gesture across the horizontal center of the screen.
+        // Note that the swipe down is a bit longer than the swipe up to check that the app bar
+        // is not starting to expand too early.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom - shortSwipeAmount,
+                3 * shortSwipeAmount / 2);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be visually snapped below the system status bar
+        // as it is in the pinned mode and we haven't fully swiped down the content below the
+        // app bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform yet another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+
+    @Test
+    public void testScrollingToolbar() {
+        configureContent(R.layout.design_appbar_toolbar_collapse_scroll,
+                R.string.design_appbar_collapsing_toolbar_scroll);
+
+        CollapsingToolbarLayout.LayoutParams toolbarLp =
+                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+                toolbarLp.getCollapseMode());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+        final int shortSwipeAmount = toolbarHeight;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should not be visually "present" on the screen, with its bottom
+        // edge aligned with the system status bar.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-up gesture
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                shortSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-down gesture across the horizontal center of the screen.
+        // Note that the swipe down is a bit longer than the swipe up to fully bring down
+        // the scrolled-away toolbar
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                3 * shortSwipeAmount / 2);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be visually snapped below the system status bar as it
+        // in scrolling mode and we've swiped down, but not fully. Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
+
+        // Perform yet another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+
+    @Test
+    public void testPinnedToolbarAndAnchoredFab() throws Throwable {
+        configureContent(R.layout.design_appbar_toolbar_collapse_pin_with_fab,
+                R.string.design_appbar_collapsing_toolbar_pin_fab);
+
+        CollapsingToolbarLayout.LayoutParams toolbarLp =
+                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+                toolbarLp.getCollapseMode());
+
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        // Since we the visibility change listener path is only exposed via direct calls to
+        // FloatingActionButton.show and not the internal path that FAB's behavior is using,
+        // this test needs to be tied to the internal implementation details of running animation
+        // that scales the FAB to 0/0 scales and interpolates its alpha to 0. Since that animation
+        // starts running partway through our swipe gesture and may complete a bit later then
+        // the swipe gesture, sleep for a bit to catch the "final" state of the FAB.
+        SystemClock.sleep(200);
+
+        // At this point the FAB should be scaled to 0/0 and set at alpha 0. Since the relevant
+        // getter methods are only available on v11+, wrap the asserts with build version check.
+        if (Build.VERSION.SDK_INT >= 11) {
+            assertEquals(0.0f, fab.getScaleX(), 0.0f);
+            assertEquals(0.0f, fab.getScaleY(), 0.0f);
+            assertEquals(0.0f, fab.getAlpha(), 0.0f);
+        }
+
+        // Perform a swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        // Same as for swipe-up gesture - sleep for a bit to catch the "final" visible state of
+        // the FAB.
+        SystemClock.sleep(200);
+
+        // At this point the FAB should be scaled back to its original size and be at full opacity.
+        if (Build.VERSION.SDK_INT >= 11) {
+            assertEquals(1.0f, fab.getScaleX(), 0.0f);
+            assertEquals(1.0f, fab.getScaleY(), 0.0f);
+            assertEquals(1.0f, fab.getAlpha(), 0.0f);
+        }
+    }
+
+    @Test
+    public void testPinnedToolbarAndParallaxImage() {
+        configureContent(R.layout.design_appbar_toolbar_collapse_with_image,
+                R.string.design_appbar_collapsing_toolbar_with_image);
+
+        final ImageView parallaxImageView =
+                (ImageView) mCoordinatorLayout.findViewById(R.id.app_bar_image);
+
+        CollapsingToolbarLayout.LayoutParams parallaxImageViewLp =
+                (CollapsingToolbarLayout.LayoutParams) parallaxImageView.getLayoutParams();
+        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PARALLAX,
+                parallaxImageViewLp.getCollapseMode());
+
+        final float parallaxMultiplier = parallaxImageViewLp.getParallaxMultiplier();
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] parallaxImageOnScreenXY = new int[2];
+        final int appbarHeight = mAppBar.getHeight();
+        final int toolbarHeight = mToolbar.getHeight();
+        final int parallaxImageHeight = parallaxImageView.getHeight();
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int originalParallaxImageTop = parallaxImageOnScreenXY[1];
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        // Test that at the beginning our image is top-aligned with the app bar
+        assertEquals(appbarOnScreenXY[1], parallaxImageOnScreenXY[1]);
+
+        // Swipe up by the toolbar's height
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                toolbarHeight);
+
+        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+        // the amount that the top edge of the app bar (in the screen coordinates) has.
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+
+        // Swipe up by another toolbar's height
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                toolbarHeight);
+
+        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+        // the amount that the top edge of the app bar (in the screen coordinates) has.
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+
+        // Swipe down by a different value (150% of the toolbar's height) to test parallax going the
+        // other way
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                3 * toolbarHeight / 2);
+
+        // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+        // the amount that the top edge of the app bar (in the screen coordinates) has.
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+        assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+                parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
new file mode 100644
index 0000000..d3529cc
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
@@ -0,0 +1,385 @@
+/*
+ * 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.design.widget;
+
+import android.support.annotation.LayoutRes;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.Cheeses;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import org.junit.Test;
+
+import static android.support.design.testutils.TestUtilsActions.addTabs;
+import static android.support.design.testutils.TestUtilsActions.waitUntilIdle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+public class AppBarWithToolbarAndTabsTest extends AppBarLayoutBaseTest {
+    private TabLayout mTabLayout;
+
+    @Override
+    protected void configureContent(@LayoutRes int layoutResId, @StringRes int titleResId) {
+        super.configureContent(layoutResId, titleResId);
+
+        mTabLayout = (TabLayout) mAppBar.findViewById(R.id.tabs);
+        String[] tabTitles = new String[5];
+        System.arraycopy(Cheeses.sCheeseStrings, 0, tabTitles, 0, 5);
+        onView(withId(R.id.tabs)).perform(addTabs(tabTitles));
+    }
+
+    @Test
+    public void testScrollingToolbarAndScrollingTabs() {
+        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll,
+                R.string.design_appbar_toolbar_scroll_tabs_scroll);
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int tabsHeight = mTabLayout.getHeight();
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+        final int shortSwipeAmount = toolbarHeight;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should not be visually "present" on the screen, with its bottom
+        // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-up gesture
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                shortSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a long swipe-down gesture across the horizontal center of the screen.
+        // Note that the swipe down is a bit longer than the swipe up to fully bring down
+        // the scrolled-away toolbar and tab layout
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount * 3 / 2);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be visually snapped below the system status bar as it
+        // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform yet another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+
+    @Test
+    public void testScrollingToolbarAndPinnedTabs() {
+        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_pinned,
+                R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int tabsHeight = mTabLayout.getHeight();
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+        final int shortSwipeAmount = toolbarHeight;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the tab bar should be visually snapped below the system status bar.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-up gesture
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                shortSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the tab bar should still be visually snapped below the system status bar
+        // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-down gesture across the horizontal center of the screen.
+        // Note that the swipe down is a bit longer than the swipe up to fully bring down
+        // the scrolled-away toolbar and tab layout
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom - shortSwipeAmount,
+                3 * shortSwipeAmount / 2);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position as it
+        // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform yet another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+
+    @LargeTest
+    @Test
+    public void testSnappingToolbarAndSnappingTabs() {
+        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll_snap,
+                R.string.design_appbar_toolbar_scroll_tabs_scroll_snap);
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] coordinatorLayoutOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int tabsHeight = mTabLayout.getHeight();
+        final int appbarHeight = mAppBar.getHeight();
+
+        // Since AppBarLayout doesn't expose a way to track snap animations, the three possible
+        // options are
+        // a) track how vertical offsets and invalidation is propagated through the
+        // view hierarchy and wait until there are no more events of that kind
+        // b) run a dummy Espresso action that waits until the main thread is idle
+        // c) sleep for a hardcoded period of time to "wait" until the snap animation is done
+        // In this test method we go with option b)
+
+        // Perform a swipe-up gesture across the horizontal center of the screen. The amount
+        // of swipe is 25% of the toolbar height and we expect the snap behavior to "move"
+        // the app bar back to its original position.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                toolbarHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position as it
+        // in snapping mode and we haven't swiped "enough". Allow for off-by-a-pixel
+        // margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a slightly longer swipe-up gesture, this time by 75% of the toolbar height.
+        // We expect the snap behavior to move the app bar to snap the tab layout below the
+        // system status bar.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                3 * toolbarHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should "snap" the toolbar away and align the tab layout below
+        // the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-up gesture, this time by 25% of the tab layout height. We expect
+        // snap behavior to move the app bar back to snap the tab layout below the system status
+        // bar.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                tabsHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should "snap" back to align the tab layout below
+        // the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
+        // snap behavior to move the app bar fully away from the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                3 * tabsHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should not be visually "present" on the screen, with its bottom
+        // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-down gesture by 25% of the tab layout height. We expect
+        // snap behavior to move the app bar back fully away from the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                tabsHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still not be visually "present" on the screen, with
+        // its bottom edge aligned with the system status bar. Allow for off-by-a-pixel margin
+        // of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
+        // snap behavior to move the app bar to snap the tab layout below the system status
+        // bar.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                3 * tabsHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should "snap" the toolbar away and align the tab layout below
+        // the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a short swipe-down gesture by 25% of the toolbar height. We expect
+        // snap behavior to align the tab layout below the system status bar
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                toolbarHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should still align the tab layout below
+        // the system status bar. Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform a longer swipe-up gesture, this time by 75% of the toolbar height. We expect
+        // snap behavior to move the app bar back to its original place (fully visible).
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + toolbarHeight,
+                3 * tabsHeight / 4);
+
+        // Wait for the snap animation to be done
+        onView(isRoot()).perform(waitUntilIdle());
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        // At this point the app bar should be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java
new file mode 100644
index 0000000..22bf268
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.design.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import org.junit.Test;
+
+@MediumTest
+public class AppBarWithToolbarTest extends AppBarLayoutBaseTest {
+
+    /**
+     * Tests a Toolbar with fitSystemWindows = undefined, with a fitSystemWindows = true parent
+     */
+    @Test
+    public void testScrollToolbarWithFitSystemWindowsParent() {
+        configureContent(R.layout.design_appbar_toolbar_scroll_fitsystemwindows_parent,
+                R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+        final int[] appbarOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int appbarHeight = mAppBar.getHeight();
+        final int longSwipeAmount = 3 * appbarHeight / 2;
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + 3 * longSwipeAmount / 2,
+                longSwipeAmount);
+
+        // At this point the tab bar should be visually snapped below the system status bar.
+        // Allow for off-by-a-pixel margin of error.
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+        // Perform yet another swipe-down gesture across the horizontal center of the screen.
+        performVerticalSwipeDownGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom,
+                longSwipeAmount);
+
+        // At this point the app bar should still be in its original position.
+        // Allow for off-by-a-pixel margin of error.
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+    }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java
new file mode 100755
index 0000000..9239940
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.support.annotation.LayoutRes;
+import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+import android.view.ViewStub;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static org.hamcrest.Matchers.allOf;
+
+/**
+ * Base class for tests that are exercising various aspects of {@link CoordinatorLayout}.
+ */
+public abstract class BaseDynamicCoordinatorLayoutTest
+        extends BaseInstrumentationTestCase<DynamicCoordinatorLayoutActivity> {
+    protected CoordinatorLayout mCoordinatorLayout;
+
+    public BaseDynamicCoordinatorLayoutTest() {
+        super(DynamicCoordinatorLayoutActivity.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Now that the test is done, replace the activity content view with ViewStub so
+        // that it's ready to be replaced for the next test.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final DynamicCoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+                activity.setContentView(R.layout.dynamic_coordinator_layout);
+                mCoordinatorLayout = null;
+            }
+        });
+    }
+
+    /**
+     * Matches views that have parents.
+     */
+    private Matcher<View> hasParent() {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has parent");
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                return view.getParent() != null;
+            }
+        };
+    }
+
+    /**
+     * Inflates the <code>ViewStub</code> with the passed layout resource.
+     */
+    protected ViewAction inflateViewStub(final @LayoutRes int layoutResId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return allOf(isAssignableFrom(ViewStub.class), hasParent());
+            }
+
+            @Override
+            public String getDescription() {
+                return "Inflates view stub";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                ViewStub viewStub = (ViewStub) view;
+                viewStub.setLayoutResource(layoutResId);
+                viewStub.inflate();
+
+                mCoordinatorLayout = (CoordinatorLayout) mActivityTestRule.getActivity()
+                        .findViewById(viewStub.getInflatedId());
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/BaseTestActivity.java b/design/tests/src/android/support/design/widget/BaseTestActivity.java
index 164a191..0c801cb 100755
--- a/design/tests/src/android/support/design/widget/BaseTestActivity.java
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -27,6 +27,8 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
+
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         final int contentView = getContentViewLayoutResId();
         if (contentView > 0) {
@@ -36,6 +38,12 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
+
     protected abstract int getContentViewLayoutResId();
 
     protected void onContentViewSet() {}
diff --git a/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
new file mode 100644
index 0000000..3b06ae2
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.design.widget;
+
+import android.os.Build;
+import android.support.design.custom.TestFloatingBehavior;
+import android.support.design.test.R;
+import android.support.design.testutils.SnackbarUtils;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v7.widget.AppCompatTextView;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+@MediumTest
+public class CoordinatorSnackbarWithFabTest extends BaseDynamicCoordinatorLayoutTest {
+    private static final String MESSAGE_TEXT = "Test Message";
+    private static final String ACTION_TEXT = "Action";
+
+    private Snackbar mSnackbar;
+
+    @After
+    public void teardown() {
+        // Dismiss the snackbar to get back to clean state for the next test
+        if (mSnackbar != null) {
+            SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(mSnackbar);
+        }
+    }
+
+    /**
+     * Returns the location of our snackbar on the screen.
+     */
+    private static int[] getSnackbarLocationOnScreen() {
+        final int[] location = new int[2];
+        onView(isAssignableFrom(Snackbar.SnackbarLayout.class)).perform(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isEnabled();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Snackbar matcher";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                view.getLocationOnScreen(location);
+            }
+        });
+        return location;
+    }
+
+    /**
+     * Helper method that verifies that the passed view is above the snackbar in the activity
+     * window.
+     */
+    private static void verifySnackbarViewStacking(View view, int extraBottomMargin) {
+        if (Build.VERSION.SDK_INT >= 11) {
+            // Get location of snackbar in window
+            final int[] snackbarOnScreenXY = getSnackbarLocationOnScreen();
+            // Get location of passed view in window
+            final int[] viewOnScreenXY = new int[2];
+            view.getLocationOnScreen(viewOnScreenXY);
+
+            // Compute the bottom visible edge of the view
+            int viewBottom = viewOnScreenXY[1] + view.getHeight() - extraBottomMargin;
+            int snackbarTop = snackbarOnScreenXY[1];
+            // and verify that our view is above the snackbar
+            assertTrue(viewBottom <= snackbarTop);
+        }
+    }
+
+    @Test
+    public void testBuiltInSliding() {
+        onView(withId(R.id.coordinator_stub)).perform(
+                inflateViewStub(R.layout.design_snackbar_with_fab));
+
+        // Create and show a snackbar
+        mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+        // Take into account bottom padding and bottom margin to account for how drop shadow is
+        // emulated on pre-Lollipop devices
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        verifySnackbarViewStacking(fab, fab.getPaddingBottom()
+                - ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin);
+    }
+
+    @Test
+    public void testBehaviorBasedSlidingFromLayoutAttribute() {
+        // Use a layout in which an AppCompatTextView child has Behavior object configured via
+        // layout_behavior XML attribute
+        onView(withId(R.id.coordinator_stub)).perform(
+                inflateViewStub(R.layout.design_snackbar_behavior_layout_attr));
+
+        // Create and show a snackbar
+        mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+        final AppCompatTextView textView =
+                (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+        verifySnackbarViewStacking(textView, 0);
+    }
+
+    @Test
+    public void testBehaviorBasedSlidingFromClassAnnotation() {
+        // Use a layout in which a custom child view has Behavior object configured via
+        // annotation on the class that extends AppCompatTextView
+        onView(withId(R.id.coordinator_stub)).perform(
+                inflateViewStub(R.layout.design_snackbar_behavior_annotation));
+
+        // Create and show a snackbar
+        mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+        final AppCompatTextView textView =
+                (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+        verifySnackbarViewStacking(textView, 0);
+    }
+
+    @Test
+    public void testBehaviorBasedSlidingFromRuntimeApiCall() {
+        // Use a layout in which an AppCompatTextView child doesn't have any configured Behavior
+        onView(withId(R.id.coordinator_stub)).perform(
+                inflateViewStub(R.layout.design_snackbar_behavior_runtime));
+
+        // and configure that Behavior at runtime by setting it on its LayoutParams
+        final AppCompatTextView textView =
+                (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+        final CoordinatorLayout.LayoutParams textViewLp =
+                (CoordinatorLayout.LayoutParams) textView.getLayoutParams();
+        textViewLp.setBehavior(new TestFloatingBehavior());
+
+        // Create and show a snackbar
+        mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+        verifySnackbarViewStacking(textView, 0);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java
similarity index 72%
copy from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
copy to design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java
index 124d683..f26ef12 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java
@@ -18,17 +18,12 @@
 
 import android.support.design.test.R;
 
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+/**
+ * Test activity for testing various aspects of {@link CoordinatorLayout}.
+ */
+public class DynamicCoordinatorLayoutActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.test_design_snackbar;
+        return R.layout.dynamic_coordinator_layout;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
-}
\ No newline at end of file
+}
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/design/tests/src/android/support/design/widget/SnackbarActivity.java
similarity index 76%
rename from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
rename to design/tests/src/android/support/design/widget/SnackbarActivity.java
index 124d683..5d8e118 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/design/tests/src/android/support/design/widget/SnackbarActivity.java
@@ -18,17 +18,9 @@
 
 import android.support.design.test.R;
 
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+public class SnackbarActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
         return R.layout.test_design_snackbar;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
 }
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java b/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
deleted file mode 100644
index db8d33a..0000000
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTests.java
+++ /dev/null
@@ -1,126 +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.design.widget;
-
-import android.os.SystemClock;
-import android.support.design.test.R;
-import android.support.test.espresso.ViewAction;
-import android.support.test.espresso.ViewInteraction;
-import android.support.test.espresso.action.ViewActions;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.View;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-public class SnackbarBucketTests extends BaseInstrumentationTestCase<SnackbarBucketTestsActivity> {
-
-    private static final String MESSAGE_TEXT = "Test Message";
-    private static final String ACTION_TEXT = "Action";
-
-    public SnackbarBucketTests() {
-        super(SnackbarBucketTestsActivity.class);
-    }
-
-    @Test
-    @MediumTest
-    public void testActionClickDismiss() {
-        testDismissCallback(
-                onView(withId(R.id.snackbar_action)),
-                ViewActions.click(),
-                Snackbar.LENGTH_LONG,
-                Snackbar.Callback.DISMISS_EVENT_ACTION);
-    }
-
-    @Test
-    @MediumTest
-    public void testSwipeDismissCallback() {
-        testDismissCallback(
-                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
-                ViewActions.swipeRight(),
-                Snackbar.LENGTH_LONG,
-                Snackbar.Callback.DISMISS_EVENT_SWIPE);
-    }
-
-    @Test
-    @MediumTest
-    public void testActionClickListener() {
-        final AtomicBoolean clicked = new AtomicBoolean();
-
-        final Snackbar snackbar = Snackbar.make(getCoordinatorLayout(), MESSAGE_TEXT,
-                Snackbar.LENGTH_SHORT)
-                .setAction(ACTION_TEXT, new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        clicked.set(true);
-                    }
-                });
-        // Now show the Snackbar
-        snackbar.show();
-        // Sleep for the animation
-        SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
-        // Perform the action click
-        onView(withId(R.id.snackbar_action)).perform(ViewActions.click());
-
-        assertTrue("Action click listener called", clicked.get());
-    }
-
-    private void testDismissCallback(final ViewInteraction interaction, final ViewAction action,
-            final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) {
-        final ArrayList<Integer> dismissEvents = new ArrayList<>();
-
-        final Snackbar snackbar = Snackbar.make(getCoordinatorLayout(), MESSAGE_TEXT, length)
-                .setAction(ACTION_TEXT, new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                        // no-op
-                    }
-                })
-                .setCallback(new Snackbar.Callback() {
-                    @Override
-                    public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
-                        dismissEvents.add(event);
-                    }
-                });
-
-        // Now show the Snackbar
-        snackbar.show();
-        // Sleep for the animation
-        SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
-        // ...and the perform the UI interaction
-        interaction.perform(action);
-        // Now wait until the Snackbar has been removed from the view hierarchy
-        while (snackbar.isShownOrQueued()) {
-            SystemClock.sleep(20);
-        }
-
-        assertTrue("onDismissed has not been called once only. Events: " + dismissEvents,
-                dismissEvents.size() == 1);
-        assertEquals(expectedEvent, (int) dismissEvents.get(0));
-    }
-
-    private CoordinatorLayout getCoordinatorLayout() {
-        return mActivityTestRule.getActivity().mCoordinatorLayout;
-    }
-}
diff --git a/design/tests/src/android/support/design/widget/SnackbarTest.java b/design/tests/src/android/support/design/widget/SnackbarTest.java
new file mode 100644
index 0000000..8a0381e
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/SnackbarTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.design.widget;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.SnackbarUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.design.testutils.TestUtilsActions.setLayoutDirection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.*;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class SnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
+    private static final String MESSAGE_TEXT = "Test Message";
+    private static final @StringRes int MESSAGE_ID = R.string.snackbar_text;
+    private static final String ACTION_TEXT = "Action";
+    private static final @StringRes int ACTION_ID = R.string.snackbar_action;
+
+    private CoordinatorLayout mCoordinatorLayout;
+
+    private static interface DismissAction {
+        void dismiss(Snackbar snackbar);
+    }
+
+    public SnackbarTest() {
+        super(SnackbarActivity.class);
+    }
+
+    @Before
+    public void setup() {
+        mCoordinatorLayout =
+                (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
+    }
+
+    private void verifySnackbarContent(final Snackbar snackbar, final String expectedMessage,
+            final String expectedAction) {
+        // Show the snackbar
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar);
+
+        // Verify that we're showing the message
+        withText(expectedMessage).matches(allOf(
+                isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                isCompletelyDisplayed()));
+
+        // If the action is not empty, verify that we're showing it
+        if (!TextUtils.isEmpty(expectedAction)) {
+            withText(expectedAction).matches(allOf(
+                    isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                    isCompletelyDisplayed()));
+        }
+
+        // Dismiss the snackbar
+        SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(snackbar);
+    }
+
+    @Test
+    @SmallTest
+    public void testBasicContent() {
+        // Verify different combinations of snackbar content (message and action) and duration
+
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final String resolvedMessage = res.getString(MESSAGE_ID);
+        final String resolvedAction = res.getString(ACTION_ID);
+
+        // String message and no action
+        verifySnackbarContent(
+                Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT),
+                MESSAGE_TEXT, null);
+
+        // Resource message and no action
+        verifySnackbarContent(
+                Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG),
+                resolvedMessage, null);
+
+        // String message and string action
+        verifySnackbarContent(
+                Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                        .setAction(ACTION_TEXT, mock(View.OnClickListener.class)),
+                MESSAGE_TEXT, ACTION_TEXT);
+
+        // String message and resource action
+        verifySnackbarContent(
+                Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
+                        .setAction(ACTION_ID, mock(View.OnClickListener.class)),
+                MESSAGE_TEXT, resolvedAction);
+
+        // Resource message and resource action
+        verifySnackbarContent(
+                Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG)
+                        .setAction(ACTION_ID, mock(View.OnClickListener.class)),
+                resolvedMessage, resolvedAction);
+    }
+
+    private void verifyDismissCallback(final ViewInteraction interaction,
+            final @Nullable ViewAction action, final @Nullable DismissAction dismissAction,
+            final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) {
+        final Snackbar.Callback mockCallback = mock(Snackbar.Callback.class);
+        final Snackbar snackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, length)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class))
+                .setCallback(mockCallback);
+
+        // Note that unlike other tests around Snackbar that use Espresso's IdlingResources
+        // to wait until the snackbar is shown (SnackbarUtils.showSnackbarAndWaitUntilFullyShown),
+        // here we want to verify our callback has been called with onShown after snackbar is shown
+        // and with onDismissed after snackbar is dismissed.
+
+        // Now show the Snackbar
+        snackbar.show();
+        // sleep for the animation
+        SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
+
+        // Now perform the UI interaction
+        if (action != null) {
+            interaction.perform(action);
+        } else if (dismissAction != null) {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    dismissAction.dismiss(snackbar);
+                }
+            });
+        }
+        // wait until the Snackbar has been removed from the view hierarchy
+        while (snackbar.isShownOrQueued()) {
+            SystemClock.sleep(20);
+        }
+        // and verify that our callback was invoked with onShown and onDismissed
+        verify(mockCallback, times(1)).onShown(snackbar);
+        verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent);
+        verifyNoMoreInteractions(mockCallback);
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaActionClick() {
+        verifyDismissCallback(
+                onView(withId(R.id.snackbar_action)),
+                click(),
+                null,
+                Snackbar.LENGTH_LONG,
+                Snackbar.Callback.DISMISS_EVENT_ACTION);
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaSwipe() {
+        verifyDismissCallback(
+                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                swipeRight(),
+                null,
+                Snackbar.LENGTH_LONG,
+                Snackbar.Callback.DISMISS_EVENT_SWIPE);
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaSwipeRtl() {
+        onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+        if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+            // On devices that support RTL layout, the start-to-end dismiss swipe is done
+            // with swipeLeft() action
+            verifyDismissCallback(
+                    onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                    swipeLeft(),
+                    null,
+                    Snackbar.LENGTH_LONG,
+                    Snackbar.Callback.DISMISS_EVENT_SWIPE);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaApi() {
+        verifyDismissCallback(
+                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                null,
+                new DismissAction() {
+                    @Override
+                    public void dismiss(Snackbar snackbar) {
+                        snackbar.dismiss();
+                    }
+                },
+                Snackbar.LENGTH_LONG,
+                Snackbar.Callback.DISMISS_EVENT_MANUAL);
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaTimeout() {
+        verifyDismissCallback(
+                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                null,
+                null,
+                Snackbar.LENGTH_LONG,
+                Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
+    }
+
+    @Test
+    @MediumTest
+    public void testDismissViaAnotherSnackbar() {
+        final Snackbar anotherSnackbar =
+                Snackbar.make(mCoordinatorLayout, "A different message", Snackbar.LENGTH_SHORT);
+
+        // Our dismiss action is to show another snackbar (and verify that the original snackbar
+        // is now dismissed with CONSECUTIVE event)
+        verifyDismissCallback(
+                onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+                null,
+                new DismissAction() {
+                    @Override
+                    public void dismiss(Snackbar snackbar) {
+                        anotherSnackbar.show();
+                    }
+                },
+                Snackbar.LENGTH_LONG,
+                Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE);
+
+        // And dismiss the second snackbar to get back to clean state
+        SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(anotherSnackbar);
+    }
+
+    @Test
+    @MediumTest
+    public void testActionClickListener() {
+        final View.OnClickListener mockClickListener = mock(View.OnClickListener.class);
+        final Snackbar snackbar =
+                Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
+                    .setAction(ACTION_TEXT, mockClickListener);
+
+        // Show the snackbar
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar);
+        // perform the action click
+        onView(withId(R.id.snackbar_action)).perform(click());
+        // and verify that our click listener has been called
+        verify(mockClickListener, times(1)).onClick(any(View.class));
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java b/design/tests/src/android/support/design/widget/TabLayoutTest.java
similarity index 63%
rename from design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java
rename to design/tests/src/android/support/design/widget/TabLayoutTest.java
index 7ef5cff..fcbf370 100755
--- a/design/tests/src/android/support/design/widget/TabLayoutWithLayoutItems.java
+++ b/design/tests/src/android/support/design/widget/TabLayoutTest.java
@@ -30,14 +30,13 @@
 
 import org.junit.Test;
 
-public class TabLayoutWithLayoutItems extends BaseInstrumentationTestCase<AppCompatActivity> {
-
-    public TabLayoutWithLayoutItems() {
+@SmallTest
+public class TabLayoutTest extends BaseInstrumentationTestCase<AppCompatActivity> {
+    public TabLayoutTest() {
         super(AppCompatActivity.class);
     }
 
     @Test
-    @SmallTest
     public void testInflateTabLayoutWithTabItems() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
@@ -73,7 +72,6 @@
     }
 
     @Test
-    @SmallTest
     public void testInflateTabLayoutWithNonTabItem() throws Throwable {
         final Throwable[] exceptions = new Throwable[1];
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@@ -94,4 +92,48 @@
         assertTrue(thrown instanceof InflateException
                 || thrown instanceof IllegalArgumentException);
     }
+
+    @Test
+    public void testTabWithCustomLayoutSelection1() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final LayoutInflater inflater =
+                        LayoutInflater.from(mActivityTestRule.getActivity());
+
+                final TabLayout tabLayout =
+                        (TabLayout) inflater.inflate(R.layout.design_tabs, null);
+                final TabLayout.Tab tab = tabLayout.newTab();
+                tab.setCustomView(R.layout.design_tab_item_custom);
+                tabLayout.addTab(tab);
+
+                assertNotNull("Tab has custom view", tab.getCustomView());
+                assertEquals("First tab is selected", 0, tabLayout.getSelectedTabPosition());
+                assertTrue("Custom view for first tab is selected",
+                        tab.getCustomView().isSelected());
+            }
+        });
+    }
+
+    @Test
+    public void testTabWithCustomLayoutSelection2() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final LayoutInflater inflater =
+                        LayoutInflater.from(mActivityTestRule.getActivity());
+
+                final TabLayout tabLayout =
+                        (TabLayout) inflater.inflate(R.layout.design_tabs, null);
+                final TabLayout.Tab tab = tabLayout.newTab();
+                tabLayout.addTab(tab);
+                tab.setCustomView(R.layout.design_tab_item_custom);
+
+                assertNotNull("Tab has custom view", tab.getCustomView());
+                assertEquals("First tab is selected", 0, tabLayout.getSelectedTabPosition());
+                assertTrue("Custom view for first tab is selected",
+                        tab.getCustomView().isSelected());
+            }
+        });
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
index 15d3780..4b4c8c9 100755
--- a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
@@ -39,12 +39,15 @@
 
 import java.util.ArrayList;
 
+import static android.support.design.testutils.TabLayoutActions.setupWithViewPager;
+import static android.support.design.testutils.ViewPagerActions.notifyAdapterContentChange;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.*;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 
 public class TabLayoutWithViewPagerTest
         extends BaseInstrumentationTestCase<TabLayoutWithViewPagerActivity> {
@@ -182,9 +185,11 @@
         onView(withId(R.id.tabs_viewpager)).perform(
                 ViewPagerActions.setAdapter(mDefaultPagerAdapter),
                 ViewPagerActions.scrollToPage(0));
+    }
 
+    private void setupTabLayoutWithViewPager() {
         // And wire the tab layout to it
-        onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
     }
 
     /**
@@ -253,6 +258,8 @@
     @Test
     @SmallTest
     public void testBasics() {
+        setupTabLayoutWithViewPager();
+
         final int itemCount = mViewPager.getAdapter().getCount();
 
         assertEquals("Matching item count", itemCount, mTabLayout.getTabCount());
@@ -271,6 +278,8 @@
     @Test
     @SmallTest
     public void testInteraction() {
+        setupTabLayoutWithViewPager();
+
         assertEquals("Default selected page", 0, mViewPager.getCurrentItem());
         assertEquals("Default selected tab", 0, mTabLayout.getSelectedTabPosition());
 
@@ -280,16 +289,18 @@
     @Test
     @SmallTest
     public void testAdapterContentChange() {
+        setupTabLayoutWithViewPager();
+
         // Verify that we have the expected initial adapter
         PagerAdapter initialAdapter = mViewPager.getAdapter();
         assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass());
         assertEquals("Initial adapter page count", 3, initialAdapter.getCount());
 
         // Add two more entries to our adapter
-        final int newItemCount = 5;
         mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
         mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
-        onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.notifyAdapterContentChange());
+        final int newItemCount = mDefaultPagerAdapter.getCount();
+        onView(withId(R.id.tabs_viewpager)).perform(notifyAdapterContentChange());
 
         // We have more comprehensive test coverage for changing the ViewPager adapter in v4/tests.
         // Here we are focused on testing the continuous integration of TabLayout with the new
@@ -308,7 +319,56 @@
 
     @Test
     @SmallTest
+    public void testAdapterContentChangeWithAutoRefreshDisabled() {
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+        // Verify that we have the expected initial adapter
+        PagerAdapter initialAdapter = mViewPager.getAdapter();
+        assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass());
+        assertEquals("Initial adapter page count", 3, initialAdapter.getCount());
+
+        // Add two more entries to our adapter
+        mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+        mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+        final int newItemCount = mDefaultPagerAdapter.getCount();
+
+        // Notify the adapter that it has changed
+        onView(withId(R.id.tabs_viewpager)).perform(notifyAdapterContentChange());
+
+        // Assert that the TabLayout did not update and add the new items
+        assertNotEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+    }
+
+    @Test
+    @SmallTest
+    public void testBasicAutoRefreshDisabled() {
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+        // Check that the TabLayout has the same number of items are the adapter
+        PagerAdapter initialAdapter = mViewPager.getAdapter();
+        assertEquals("Initial adapter page count", initialAdapter.getCount(),
+                mTabLayout.getTabCount());
+
+        // Add two more entries to our adapter
+        mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+        mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+        final int newItemCount = mDefaultPagerAdapter.getCount();
+
+        // Assert that the TabLayout did not update and add the new items
+        assertNotEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+
+        // Now setup again to update the tabs
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+        // Assert that the TabLayout updated and added the new items
+        assertEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+    }
+
+    @Test
+    @SmallTest
     public void testAdapterChange() {
+        setupTabLayoutWithViewPager();
+
         // Verify that we have the expected initial adapter
         PagerAdapter initialAdapter = mViewPager.getAdapter();
         assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass());
@@ -325,7 +385,7 @@
                 ViewPagerActions.scrollToPage(0));
 
         // As TabLayout doesn't track adapter changes, we need to re-wire the new adapter
-        onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
 
         // We have more comprehensive test coverage for changing the ViewPager adapter in v4/tests.
         // Here we are focused on testing the integration of TabLayout with the new
@@ -350,7 +410,7 @@
         // And set it on the ViewPager
         onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.setAdapter(newAdapter));
         // As TabLayout doesn't track adapter changes, we need to re-wire the new adapter
-        onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
 
         // Set fixed mode on the TabLayout
         onView(withId(R.id.tabs)).perform(TabLayoutActions.setTabMode(TabLayout.MODE_FIXED));
@@ -360,7 +420,7 @@
         for (int i = 0; i < 8; i++) {
             newAdapter.add("Title " + i, "Body " + i);
             onView(withId(R.id.tabs_viewpager)).perform(
-                    ViewPagerActions.notifyAdapterContentChange());
+                    notifyAdapterContentChange());
 
             int expectedTabCount = i + 1;
             assertEquals("Tab count after adding #" + i, expectedTabCount,
@@ -394,6 +454,8 @@
      */
     private void verifyMinMaxTabWidth(@LayoutRes int tabLayoutResId, @DimenRes int tabMinWidthResId,
             @DimenRes int tabMaxWidthResId) {
+        setupTabLayoutWithViewPager();
+
         assertEquals("Scrollable tab mode", TabLayout.MODE_SCROLLABLE, mTabLayout.getTabMode());
 
         final Resources res = mActivityTestRule.getActivity().getResources();
@@ -411,7 +473,7 @@
         onView(withId(R.id.container)).perform(TestUtilsActions.replaceTabLayout(tabLayoutResId));
 
         // Now that we have a new TabLayout, wire it to the new content of our ViewPager
-        onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+        onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
 
         // Since TabLayout doesn't expose a getter for fetching the configured max tab width,
         // start adding a variety of tabs with progressively longer tab titles and test that
@@ -429,7 +491,7 @@
             final String tabTitle = tabTitleBuilder.toString();
             newAdapter.add(tabTitle, "Body " + i);
             onView(withId(R.id.tabs_viewpager)).perform(
-                    ViewPagerActions.notifyAdapterContentChange());
+                    notifyAdapterContentChange());
 
             int expectedTabCount = i + 1;
             // Check that all tabs are at least as wide as min width *and* at most as wide as max
@@ -480,4 +542,18 @@
         verifyMinMaxTabWidth(R.layout.tab_layout_bound_minmax, R.dimen.tab_width_limit_small,
                 R.dimen.tab_width_limit_large);
     }
+
+    @Test
+    @SmallTest
+    public void testSetupAfterViewPagerScrolled() {
+        // Scroll to the last item
+        final int selected = mViewPager.getAdapter().getCount() - 1;
+        onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollToPage(selected));
+
+        // Now setup the TabLayout with the ViewPager
+        setupTabLayoutWithViewPager();
+
+        assertEquals("Selected page", selected, mViewPager.getCurrentItem());
+        assertEquals("Selected tab", selected, mTabLayout.getSelectedTabPosition());
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/design/tests/src/android/support/design/widget/TextInputLayoutActivity.java
similarity index 72%
copy from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
copy to design/tests/src/android/support/design/widget/TextInputLayoutActivity.java
index 124d683..1ae3a29 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutActivity.java
@@ -13,22 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.support.design.widget;
 
 import android.support.design.test.R;
+import android.support.v7.widget.Toolbar;
 
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+public class TextInputLayoutActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.test_design_snackbar;
+        return R.layout.design_text_input;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
-}
\ No newline at end of file
+}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
new file mode 100755
index 0000000..558f474
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.design.widget;
+
+import static android.support.design.testutils.TextInputLayoutActions.setError;
+import static android.support.design.testutils.TextInputLayoutActions.setErrorEnabled;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withChild;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.CoreMatchers.not;
+
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TextInputLayoutTest extends BaseInstrumentationTestCase<TextInputLayoutActivity> {
+
+    private static final String ERROR_MESSAGE_1 = "An error has occured";
+    private static final String ERROR_MESSAGE_2 = "Some other error has occured";
+
+    public TextInputLayoutTest() {
+        super(TextInputLayoutActivity.class);
+    }
+
+    @Test
+    public void testSetErrorEnablesErrorIsDisplayed() {
+        onView(withId(R.id.textinput)).perform(setError(ERROR_MESSAGE_1));
+        onView(withText(ERROR_MESSAGE_1)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void testDisabledErrorIsNotDisplayed() {
+        // First show an error, and then disable error functionality
+        onView(withId(R.id.textinput))
+                .perform(setError(ERROR_MESSAGE_1))
+                .perform(setErrorEnabled(false));
+
+        // Check that the error is no longer there
+        onView(withText(ERROR_MESSAGE_1)).check(doesNotExist());
+    }
+
+    @Test
+    public void testSetErrorOnDisabledSetErrorIsDisplayed() {
+        // First show an error, and then disable error functionality
+        onView(withId(R.id.textinput))
+                .perform(setError(ERROR_MESSAGE_1))
+                .perform(setErrorEnabled(false));
+
+        // Now show a different error message
+        onView(withId(R.id.textinput)).perform(setError(ERROR_MESSAGE_2));
+        // And check that it is displayed
+        onView(withText(ERROR_MESSAGE_2)).check(matches(isDisplayed()));
+    }
+
+}
diff --git a/v7/gridlayout/tests/Android.mk b/documents-archive/Android.mk
similarity index 60%
copy from v7/gridlayout/tests/Android.mk
copy to documents-archive/Android.mk
index 27587d1..44c9f66 100644
--- a/v7/gridlayout/tests/Android.mk
+++ b/documents-archive/Android.mk
@@ -14,26 +14,16 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
+LOCAL_MODULE := android-support-documents-archive
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-
+LOCAL_AIDL_INCLUDES := $LOCAL_PATH/src
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/res \
-    $(LOCAL_PATH)/../res
-
-LOCAL_STATIC_JAVA_LIBRARIES  := \
-        android-support-v7-gridlayout \
-        android-support-v4
-
-LOCAL_PACKAGE_NAME := GridLayoutTests
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v7.gridlayout
-
-include $(BUILD_PACKAGE)
-
+LOCAL_JAVA_LIBRARIES := android-support-annotations \
+    android-support-v4
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/appcompat/res/values-h320dp/bools.xml b/documents-archive/AndroidManifest.xml
similarity index 77%
rename from v7/appcompat/res/values-h320dp/bools.xml
rename to documents-archive/AndroidManifest.xml
index 5576c18..2cd0f7a 100644
--- a/v7/appcompat/res/values-h320dp/bools.xml
+++ b/documents-archive/AndroidManifest.xml
@@ -13,7 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources>
-    <bool name="abc_allow_stacked_button_bar">true</bool>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.provider">
+    <uses-sdk android:minSdkVersion="19"/>
+    <application />
+</manifest>
diff --git a/documents-archive/api/current.txt b/documents-archive/api/current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/current.txt
diff --git a/documents-archive/api/removed.txt b/documents-archive/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/removed.txt
diff --git a/documents-archive/src/android/support/provider/DocumentArchive.java b/documents-archive/src/android/support/provider/DocumentArchive.java
new file mode 100644
index 0000000..dc9a7b3
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchive.java
@@ -0,0 +1,501 @@
+/*
+ * 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.provider;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.UnsupportedOperationException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider. The id delimiter
+ * must be a character which is not used in document ids generated by the
+ * document provider.
+ *
+ * <p>This class is thread safe.
+ *
+ * @hide
+ */
+public class DocumentArchive implements Closeable {
+    private static final String TAG = "DocumentArchive";
+
+    private static final String[] DEFAULT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID,
+            Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_MIME_TYPE,
+            Document.COLUMN_SIZE,
+            Document.COLUMN_FLAGS
+    };
+
+    private final Context mContext;
+    private final String mDocumentId;
+    private final char mIdDelimiter;
+    private final Uri mNotificationUri;
+    private final ZipFile mZipFile;
+    private final ExecutorService mExecutor;
+    private final Map<String, ZipEntry> mEntries;
+    private final Map<String, List<ZipEntry>> mTree;
+
+    private DocumentArchive(
+            Context context,
+            File file,
+            String documentId,
+            char idDelimiter,
+            @Nullable Uri notificationUri)
+            throws IOException {
+        mContext = context;
+        mDocumentId = documentId;
+        mIdDelimiter = idDelimiter;
+        mNotificationUri = notificationUri;
+        mZipFile = new ZipFile(file);
+        mExecutor = Executors.newSingleThreadExecutor();
+
+        // Build the tree structure in memory.
+        mTree = new HashMap<String, List<ZipEntry>>();
+        mTree.put("/", new ArrayList<ZipEntry>());
+
+        mEntries = new HashMap<String, ZipEntry>();
+        ZipEntry entry;
+        final List<? extends ZipEntry> entries = Collections.list(mZipFile.entries());
+        final Stack<ZipEntry> stack = new Stack<>();
+        for (int i = entries.size() - 1; i >= 0; i--) {
+            entry = entries.get(i);
+            if (entry.isDirectory() != entry.getName().endsWith("/")) {
+                throw new IOException(
+                        "Directories must have a trailing slash, and files must not.");
+            }
+            if (mEntries.containsKey(entry.getName())) {
+                throw new IOException("Multiple entries with the same name are not supported.");
+            }
+            mEntries.put(entry.getName(), entry);
+            if (entry.isDirectory()) {
+                mTree.put(entry.getName(), new ArrayList<ZipEntry>());
+            }
+            stack.push(entry);
+        }
+
+        int delimiterIndex;
+        String parentPath;
+        ZipEntry parentEntry;
+        List<ZipEntry> parentList;
+
+        while (stack.size() > 0) {
+            entry = stack.pop();
+
+            delimiterIndex = entry.getName().lastIndexOf('/', entry.isDirectory()
+                    ? entry.getName().length() - 2 : entry.getName().length() - 1);
+            parentPath =
+                    delimiterIndex != -1 ? entry.getName().substring(0, delimiterIndex) + "/" : "/";
+            parentList = mTree.get(parentPath);
+
+            if (parentList == null) {
+                parentEntry = mEntries.get(parentPath);
+                if (parentEntry == null) {
+                    // The ZIP file doesn't contain all directories leading to the entry.
+                    // It's rare, but can happen in a valid ZIP archive. In such case create a
+                    // fake ZipEntry and add it on top of the stack to process it next.
+                    parentEntry = new ZipEntry(parentPath);
+                    parentEntry.setSize(0);
+                    parentEntry.setTime(entry.getTime());
+                    mEntries.put(parentPath, parentEntry);
+                    stack.push(parentEntry);
+                }
+                parentList = new ArrayList<ZipEntry>();
+                mTree.put(parentPath, parentList);
+            }
+
+            parentList.add(entry);
+        }
+    }
+
+    /**
+     * Creates a DocumentsArchive instance for opening, browsing and accessing
+     * documents within the archive passed as a local file.
+     *
+     * @param context Context of the provider.
+     * @param File Local file containing the archive.
+     * @param documentId ID of the archive document.
+     * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+     *            The delimiter must never be used for IDs of other documents.
+     * @param Uri notificationUri Uri for notifying that the archive file has changed.
+     * @see createForParcelFileDescriptor(DocumentsProvider, ParcelFileDescriptor, String, char,
+     *          Uri)
+     */
+    public static DocumentArchive createForLocalFile(
+            Context context, File file, String documentId, char idDelimiter,
+            @Nullable Uri notificationUri)
+            throws IOException {
+        return new DocumentArchive(context, file, documentId, idDelimiter, notificationUri);
+    }
+
+    /**
+     * Creates a DocumentsArchive instance for opening, browsing and accessing
+     * documents within the archive passed as a file descriptor.
+     *
+     * <p>Note, that this method should be used only if the document does not exist
+     * on the local storage. A snapshot file will be created, which may be slower
+     * and consume significant resources, in contrast to using
+     * {@see createForLocalFile(Context, File, String, char, Uri}.
+     *
+     * @param context Context of the provider.
+     * @param descriptor File descriptor for the archive's contents.
+     * @param documentId ID of the archive document.
+     * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+     *            The delimiter must never be used for IDs of other documents.
+     * @param Uri notificationUri Uri for notifying that the archive file has changed.
+     * @see createForLocalFile(Context, File, String, char, Uri)
+     */
+    public static DocumentArchive createForParcelFileDescriptor(
+            Context context, ParcelFileDescriptor descriptor, String documentId,
+            char idDelimiter, @Nullable Uri notificationUri)
+            throws IOException {
+        File snapshotFile = null;
+        try {
+            // Create a copy of the archive, as ZipFile doesn't operate on streams.
+            // Moreover, ZipInputStream would be inefficient for large files on
+            // pipes.
+            snapshotFile = File.createTempFile("android.support.provider.snapshot{",
+                    "}.zip", context.getCacheDir());
+
+            try (
+                final FileOutputStream outputStream =
+                        new ParcelFileDescriptor.AutoCloseOutputStream(
+                                ParcelFileDescriptor.open(
+                                        snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY));
+                final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+                        new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
+            ) {
+                final byte[] buffer = new byte[32 * 1024];
+                int bytes;
+                while ((bytes = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytes);
+                }
+                outputStream.flush();
+                return new DocumentArchive(context, snapshotFile, documentId, idDelimiter,
+                        notificationUri);
+            }
+        } finally {
+            // On UNIX the file will be still available for processes which opened it, even
+            // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+            if (snapshotFile != null) {
+                snapshotFile.delete();
+            }
+        }
+    }
+
+    /**
+     * Lists child documents of an archive or a directory within an
+     * archive. Must be called only for archives with supported mime type,
+     * or for documents within archives.
+     *
+     * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+     */
+    public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+            @Nullable String sortOrder) throws FileNotFoundException {
+        final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+                documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+
+        final String parentPath = parsedParentId.mPath != null ? parsedParentId.mPath : "/";
+        final MatrixCursor result = new MatrixCursor(
+                projection != null ? projection : DEFAULT_PROJECTION);
+        if (mNotificationUri != null) {
+            result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+        }
+
+        final List<ZipEntry> parentList = mTree.get(parentPath);
+        if (parentList == null) {
+            throw new FileNotFoundException();
+        }
+        for (final ZipEntry entry : parentList) {
+            addCursorRow(result, entry);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a MIME type of a document within an archive.
+     *
+     * @see DocumentsProvider.getDocumentType(String)
+     */
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+                documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+        Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+        final ZipEntry entry = mEntries.get(parsedId.mPath);
+        if (entry == null) {
+            throw new FileNotFoundException();
+        }
+        return getMimeTypeForEntry(entry);
+    }
+
+    /**
+     * Returns true if a document within an archive is a child or any descendant of the archive
+     * document or another document within the archive.
+     *
+     * @see DocumentsProvider.isChildDocument(String, String)
+     */
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+                parentDocumentId, mIdDelimiter);
+        final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+                documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+        Preconditions.checkArgumentNotNull(parsedId.mPath,
+                "Not a document within an archive.");
+
+        final ZipEntry entry = mEntries.get(parsedId.mPath);
+        if (entry == null) {
+            return false;
+        }
+
+        if (parsedParentId.mPath == null) {
+            // No need to compare paths. Every file in the archive is a child of the archive
+            // file.
+            return true;
+        }
+
+        final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
+        if (parentEntry == null || !parentEntry.isDirectory()) {
+            return false;
+        }
+
+        final String parentPath = entry.getName();
+
+        // Add a trailing slash even if it's not a directory, so it's easy to check if the
+        // entry is a descendant.
+        final String pathWithSlash = entry.isDirectory() ? entry.getName() : entry.getName() + "/";
+        return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash);
+    }
+
+    /**
+     * Returns metadata of a document within an archive.
+     *
+     * @see DocumentsProvider.queryDocument(String, String[])
+     */
+    public Cursor queryDocument(String documentId, @Nullable String[] projection)
+            throws FileNotFoundException {
+        final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+                documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+        Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+        final ZipEntry entry = mEntries.get(parsedId.mPath);
+        if (entry == null) {
+            throw new FileNotFoundException();
+        }
+
+        final MatrixCursor result = new MatrixCursor(
+                projection != null ? projection : DEFAULT_PROJECTION);
+        if (mNotificationUri != null) {
+            result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+        }
+        addCursorRow(result, entry);
+        return result;
+    }
+
+    /**
+     * Opens a file within an archive.
+     *
+     * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+     */
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, @Nullable final CancellationSignal signal)
+            throws FileNotFoundException {
+        Preconditions.checkArgumentEquals("r", mode,
+                "Invalid mode. Only reading \"r\" supported, but got: \"%s\".");
+        final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+                documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+        Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+        final ZipEntry entry = mEntries.get(parsedId.mPath);
+        if (entry == null) {
+            throw new FileNotFoundException();
+        }
+
+        ParcelFileDescriptor[] pipe;
+        InputStream inputStream = null;
+        try {
+            pipe = ParcelFileDescriptor.createReliablePipe();
+            inputStream = mZipFile.getInputStream(entry);
+        } catch (IOException e) {
+            if (inputStream != null) {
+                IoUtils.closeQuietly(inputStream);
+            }
+            // Ideally we'd simply throw IOException to the caller, but for consistency
+            // with DocumentsProvider::openDocument, converting it to IllegalStateException.
+            throw new IllegalStateException("Failed to open the document.", e);
+        }
+        final ParcelFileDescriptor outputPipe = pipe[1];
+        final InputStream finalInputStream = inputStream;
+        mExecutor.execute(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
+                                new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) {
+                            try {
+                                final byte buffer[] = new byte[32 * 1024];
+                                int bytes;
+                                while ((bytes = finalInputStream.read(buffer)) != -1) {
+                                    if (Thread.interrupted()) {
+                                        throw new InterruptedException();
+                                    }
+                                    if (signal != null) {
+                                        signal.throwIfCanceled();
+                                    }
+                                    outputStream.write(buffer, 0, bytes);
+                                }
+                            } catch (IOException | InterruptedException e) {
+                                // Catch the exception before the outer try-with-resource closes the
+                                // pipe with close() instead of closeWithError().
+                                try {
+                                    outputPipe.closeWithError(e.getMessage());
+                                } catch (IOException e2) {
+                                    Log.e(TAG, "Failed to close the pipe after an error.", e2);
+                                }
+                            }
+                        } catch (OperationCanceledException e) {
+                            // Cancelled gracefully.
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to close the output stream gracefully.", e);
+                        } finally {
+                            IoUtils.closeQuietly(finalInputStream);
+                        }
+                    }
+                });
+
+        return pipe[0];
+    }
+
+    /**
+     * Opens a thumbnail of a file within an archive.
+     *
+     * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+     */
+    public AssetFileDescriptor openDocumentThumbnail(
+            String documentId, Point sizeHint, final CancellationSignal signal)
+            throws FileNotFoundException {
+        final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+        Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+                "Mismatching document ID. Expected: %s, actual: %s.");
+        Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+        Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
+                "Thumbnails only supported for image/* MIME type.");
+
+        // TODO: Extract thumbnails from EXIF.
+        final ZipEntry entry = mEntries.get(parsedId.mPath);
+        if (entry == null) {
+            throw new FileNotFoundException();
+        }
+
+        return new AssetFileDescriptor(
+                openDocument(documentId, "r", signal), 0, entry.getSize(), null);
+    }
+
+    /**
+     * Schedules a gracefully close of the archive after any opened files are closed.
+     *
+     * <p>This method does not block until shutdown. Once called, other methods should not be
+     * called.
+     */
+    @Override
+    public void close() {
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                IoUtils.closeQuietly(mZipFile);
+            }
+        });
+        mExecutor.shutdown();
+    }
+
+    private void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
+        final MatrixCursor.RowBuilder row = cursor.newRow();
+        final ParsedDocumentId parsedId = new ParsedDocumentId(mDocumentId, entry.getName());
+        row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId(mIdDelimiter));
+
+        final File file = new File(entry.getName());
+        row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+        row.add(Document.COLUMN_SIZE, entry.getSize());
+
+        final String mimeType = getMimeTypeForEntry(entry);
+        row.add(Document.COLUMN_MIME_TYPE, mimeType);
+
+        final int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0;
+        row.add(Document.COLUMN_FLAGS, flags);
+    }
+
+    private String getMimeTypeForEntry(ZipEntry entry) {
+        if (entry.isDirectory()) {
+            return Document.MIME_TYPE_DIR;
+        }
+
+        final int lastDot = entry.getName().lastIndexOf('.');
+        if (lastDot >= 0) {
+            final String extension = entry.getName().substring(lastDot + 1).toLowerCase(Locale.US);
+            final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            if (mimeType != null) {
+                return mimeType;
+            }
+        }
+
+        return "application/octet-stream";
+    }
+};
diff --git a/documents-archive/src/android/support/provider/DocumentArchiveHelper.java b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
new file mode 100644
index 0000000..6cb4218
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
@@ -0,0 +1,369 @@
+/*
+ * 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.provider;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.LruCache;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider.
+ *
+ * <p>This class is thread safe. All methods can be called on any thread without
+ * synchronization.
+ *
+ * TODO: Update the documentation. b/26047732
+ * @hide
+ */
+public class DocumentArchiveHelper implements Closeable {
+    /**
+     * Cursor column to be used for passing the local file path for documents.
+     * If it's not specified, then a snapshot will be created, which is slower
+     * and consumes more resources.
+     *
+     * <p>Type: STRING
+     */
+    public static final String COLUMN_LOCAL_FILE_PATH = "local_file_path";
+
+    private static final String TAG = "DocumentArchiveHelper";
+    private static final int OPENED_ARCHIVES_CACHE_SIZE = 4;
+    private static final String[] ZIP_MIME_TYPES = {
+            "application/zip", "application/x-zip", "application/x-zip-compressed"
+    };
+
+    private final DocumentsProvider mProvider;
+    private final char mIdDelimiter;
+
+    // @GuardedBy("mArchives")
+    private final LruCache<String, Loader> mArchives =
+            new LruCache<String, Loader>(OPENED_ARCHIVES_CACHE_SIZE) {
+                @Override
+                public void entryRemoved(boolean evicted, String key,
+                        Loader oldValue, Loader newValue) {
+                    oldValue.getWriteLock().lock();
+                    try {
+                        oldValue.get().close();
+                    } catch (FileNotFoundException e) {
+                        Log.e(TAG, "Failed to close an archive as it no longer exists.");
+                    } finally {
+                        oldValue.getWriteLock().unlock();
+                    }
+                }
+            };
+
+    /**
+     * Creates a helper for handling archived documents.
+     *
+     * @param provider Instance of a documents provider which provides archived documents.
+     * @param idDelimiter A character used to create document IDs within archives. Can be any
+     *            character which is not used in any other document ID. If your provider uses
+     *            numbers as document IDs, the delimiter can be eg. a colon. However if your
+     *            provider uses paths, then a delimiter can be any character not allowed in the
+     *            path, which is often \0.
+     */
+    public DocumentArchiveHelper(DocumentsProvider provider, char idDelimiter) {
+        mProvider = provider;
+        mIdDelimiter = idDelimiter;
+    }
+
+    /**
+     * Lists child documents of an archive or a directory within an
+     * archive. Must be called only for archives with supported mime type,
+     * or for documents within archives.
+     *
+     * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+     */
+    public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+            @Nullable String sortOrder)
+            throws FileNotFoundException {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().queryChildDocuments(documentId, projection, sortOrder);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Returns a MIME type of a document within an archive.
+     *
+     * @see DocumentsProvider.getDocumentType(String)
+     */
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().getDocumentType(documentId);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Returns true if a document within an archive is a child or any descendant of the archive
+     * document or another document within the archive.
+     *
+     * @see DocumentsProvider.isChildDocument(String, String)
+     */
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().isChildDocument(parentDocumentId, documentId);
+        } catch (FileNotFoundException e) {
+            throw new IllegalStateException(e);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Returns metadata of a document within an archive.
+     *
+     * @see DocumentsProvider.queryDocument(String, String[])
+     */
+    public Cursor queryDocument(String documentId, @Nullable String[] projection)
+            throws FileNotFoundException {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().queryDocument(documentId, projection);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Opens a file within an archive.
+     *
+     * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+     */
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, final CancellationSignal signal)
+            throws FileNotFoundException {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().openDocument(documentId, mode, signal);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Opens a thumbnail of a file within an archive.
+     *
+     * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+     */
+    public AssetFileDescriptor openDocumentThumbnail(
+            String documentId, Point sizeHint, final CancellationSignal signal)
+            throws FileNotFoundException {
+        Loader loader = null;
+        try {
+            loader = obtainInstance(documentId);
+            return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
+        } finally {
+            releaseInstance(loader);
+        }
+    }
+
+    /**
+     * Returns true if the passed document ID is for a document within an archive.
+     */
+    public boolean isArchivedDocument(String documentId) {
+        return ParsedDocumentId.hasPath(documentId, mIdDelimiter);
+    }
+
+    /**
+     * Returns true if the passed mime type is supported by the helper.
+     */
+    public boolean isSupportedArchiveType(String mimeType) {
+        for (final String zipMimeType : ZIP_MIME_TYPES) {
+            if (zipMimeType.equals(mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Closes the helper and disposes all existing archives. It will block until all ongoing
+     * operations on each opened archive are finished.
+     */
+    @Override
+    public void close() {
+        synchronized (mArchives) {
+            mArchives.evictAll();
+        }
+    }
+
+    /**
+     * Releases resources for an archive with the specified document ID. It will block until all
+     * operations on the archive are finished. If not opened, the method does nothing.
+     *
+     * <p>Calling this method is optional. The helper automatically closes the least recently used
+     * archives if too many archives are opened.
+     *
+     * @param archiveDocumentId ID of the archive file.
+     */
+    public void closeArchive(String documentId) {
+        synchronized (mArchives) {
+            mArchives.remove(documentId);
+        }
+    }
+
+    private Loader obtainInstance(String documentId) throws FileNotFoundException {
+        Loader loader;
+        synchronized (mArchives) {
+            loader = getInstanceUncheckedLocked(documentId);
+            loader.getReadLock().lock();
+        }
+        return loader;
+    }
+
+    private void releaseInstance(@Nullable Loader loader) {
+        if (loader != null) {
+            loader.getReadLock().unlock();
+        }
+    }
+
+    private Loader getInstanceUncheckedLocked(String documentId)
+            throws FileNotFoundException {
+        try {
+            final ParsedDocumentId id = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+            if (mArchives.get(id.mArchiveId) != null) {
+                return mArchives.get(id.mArchiveId);
+            }
+
+            final Cursor cursor = mProvider.queryDocument(id.mArchiveId, new String[]
+                    { Document.COLUMN_MIME_TYPE, COLUMN_LOCAL_FILE_PATH });
+            cursor.moveToFirst();
+            final String mimeType = cursor.getString(cursor.getColumnIndex(
+                    Document.COLUMN_MIME_TYPE));
+            Preconditions.checkArgument(isSupportedArchiveType(mimeType),
+                    "Unsupported archive type.");
+            final int columnIndex = cursor.getColumnIndex(COLUMN_LOCAL_FILE_PATH);
+            final String localFilePath = columnIndex != -1 ? cursor.getString(columnIndex) : null;
+            final File localFile = localFilePath != null ? new File(localFilePath) : null;
+            final Uri notificationUri = cursor.getNotificationUri();
+            final Loader loader = new Loader(mProvider, localFile, id, mIdDelimiter,
+                    notificationUri);
+
+            // Remove the instance from mArchives collection once the archive file changes.
+            if (notificationUri != null) {
+                final LruCache<String, Loader> finalArchives = mArchives;
+                mProvider.getContext().getContentResolver().registerContentObserver(notificationUri,
+                        false,
+                        new ContentObserver(null) {
+                            @Override
+                            public void onChange(boolean selfChange, Uri uri) {
+                                synchronized (mArchives) {
+                                    final Loader currentLoader = mArchives.get(id.mArchiveId);
+                                    if (currentLoader == loader) {
+                                        mArchives.remove(id.mArchiveId);
+                                    }
+                                }
+                            }
+                        });
+            }
+
+            mArchives.put(id.mArchiveId, loader);
+            return loader;
+        } catch (IOException e) {
+            // DocumentsProvider doesn't use IOException. For consistency convert it to
+            // IllegalStateException.
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Loads an instance of DocumentArchive lazily.
+     */
+    private static final class Loader {
+        private final DocumentsProvider mProvider;
+        private final File mLocalFile;
+        private final ParsedDocumentId mId;
+        private final char mIdDelimiter;
+        private final Uri mNotificationUri;
+        private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+        private DocumentArchive mArchive = null;
+
+        Loader(DocumentsProvider provider, @Nullable File localFile, ParsedDocumentId id,
+                char idDelimiter, Uri notificationUri) {
+            this.mProvider = provider;
+            this.mLocalFile = localFile;
+            this.mId = id;
+            this.mIdDelimiter = idDelimiter;
+            this.mNotificationUri = notificationUri;
+        }
+
+        synchronized DocumentArchive get() throws FileNotFoundException {
+            if (mArchive != null) {
+                return mArchive;
+            }
+
+            try {
+                if (mLocalFile != null) {
+                    mArchive = DocumentArchive.createForLocalFile(
+                            mProvider.getContext(), mLocalFile, mId.mArchiveId, mIdDelimiter,
+                            mNotificationUri);
+                } else {
+                    mArchive = DocumentArchive.createForParcelFileDescriptor(
+                            mProvider.getContext(),
+                            mProvider.openDocument(mId.mArchiveId, "r", null /* signal */),
+                            mId.mArchiveId, mIdDelimiter, mNotificationUri);
+                }
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
+            }
+
+            return mArchive;
+        }
+
+        Lock getReadLock() {
+            return mLock.readLock();
+        }
+
+        Lock getWriteLock() {
+            return mLock.writeLock();
+        }
+    }
+}
diff --git a/documents-archive/src/android/support/provider/IoUtils.java b/documents-archive/src/android/support/provider/IoUtils.java
new file mode 100644
index 0000000..61047dc
--- /dev/null
+++ b/documents-archive/src/android/support/provider/IoUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.provider;
+
+import android.support.annotation.Nullable;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * Simple static methods to perform common IO operations.
+ * @hide
+ */
+final class IoUtils {
+    static void closeQuietly(@Nullable Closeable closeable) {
+       if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    static void closeQuietly(@Nullable InputStream stream) {
+       if (stream != null) {
+            try {
+                stream.close();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+}
diff --git a/documents-archive/src/android/support/provider/ParsedDocumentId.java b/documents-archive/src/android/support/provider/ParsedDocumentId.java
new file mode 100644
index 0000000..ee2c366
--- /dev/null
+++ b/documents-archive/src/android/support/provider/ParsedDocumentId.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.provider;
+
+/**
+ * @hide
+ */
+class ParsedDocumentId {
+    public final String mArchiveId;
+    public final String mPath;
+
+    public ParsedDocumentId(String archiveId, String path) {
+        mArchiveId = archiveId;
+        mPath = path;
+    }
+
+    static public ParsedDocumentId fromDocumentId(String documentId, char idDelimiter) {
+        final int delimiterPosition = documentId.indexOf(idDelimiter);
+        if (delimiterPosition == -1) {
+            return new ParsedDocumentId(documentId, null);
+        } else {
+            return new ParsedDocumentId(documentId.substring(0, delimiterPosition),
+                    documentId.substring((delimiterPosition + 1)));
+        }
+    }
+
+    static public boolean hasPath(String documentId, char idDelimiter) {
+        return documentId.indexOf(idDelimiter) != -1;
+    }
+
+    public String toDocumentId(char idDelimiter) {
+        if (mPath == null) {
+            return mArchiveId;
+        } else {
+            return mArchiveId + idDelimiter + mPath;
+        }
+    }
+};
diff --git a/documents-archive/src/android/support/provider/Preconditions.java b/documents-archive/src/android/support/provider/Preconditions.java
new file mode 100644
index 0000000..fd62a2e
--- /dev/null
+++ b/documents-archive/src/android/support/provider/Preconditions.java
@@ -0,0 +1,54 @@
+/*
+ * 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.provider;
+
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Collection;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state.
+ * @hide
+ */
+final class Preconditions {
+    static void checkArgument(boolean expression, String message) {
+        if (!expression) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    static void checkArgumentNotNull(Object object, String message) {
+        if (object == null) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    static void checkArgumentEquals(String expected, @Nullable String actual, String message) {
+        if (!TextUtils.equals(expected, actual)) {
+            throw new IllegalArgumentException(String.format(message, String.valueOf(expected),
+                    String.valueOf(actual)));
+        }
+    }
+
+    static void checkState(boolean expression, String message) {
+        if (!expression) {
+            throw new IllegalStateException(message);
+        }
+    }
+}
diff --git a/v7/gridlayout/tests/Android.mk b/documents-archive/tests/Android.mk
similarity index 71%
rename from v7/gridlayout/tests/Android.mk
rename to documents-archive/tests/Android.mk
index 27587d1..7269434 100644
--- a/v7/gridlayout/tests/Android.mk
+++ b/documents-archive/tests/Android.mk
@@ -17,23 +17,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/res \
-    $(LOCAL_PATH)/../res
-
-LOCAL_STATIC_JAVA_LIBRARIES  := \
-        android-support-v7-gridlayout \
-        android-support-v4
-
-LOCAL_PACKAGE_NAME := GridLayoutTests
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v7.gridlayout
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-documents-archive
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_AAPT_FLAGS := --auto-add-overlay -0 zip
+LOCAL_PACKAGE_NAME := AndroidSupportDocumentsArchiveTests
 
 include $(BUILD_PACKAGE)
-
diff --git a/documents-archive/tests/AndroidManifest.xml b/documents-archive/tests/AndroidManifest.xml
new file mode 100644
index 0000000..47da733
--- /dev/null
+++ b/documents-archive/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.support.provider.tests">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <provider
+            android:name="android.support.provider.tests.StubProvider"
+            android:authorities="android.support.provider.tests.mystubprovider"
+            android:grantUriPermissions="true"
+            android:exported="true"
+            android:permission="android.permission.MANAGE_DOCUMENTS" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="android.support.provider.tests"
+        android:label="Tests for android.support.provider." />
+</manifest>
diff --git a/documents-archive/tests/res/raw/archive.zip b/documents-archive/tests/res/raw/archive.zip
new file mode 100644
index 0000000..c3b8d22
--- /dev/null
+++ b/documents-archive/tests/res/raw/archive.zip
Binary files differ
diff --git a/documents-archive/tests/res/raw/empty_dirs.zip b/documents-archive/tests/res/raw/empty_dirs.zip
new file mode 100644
index 0000000..1dd2251
--- /dev/null
+++ b/documents-archive/tests/res/raw/empty_dirs.zip
Binary files differ
diff --git a/documents-archive/tests/res/raw/no_dirs.zip b/documents-archive/tests/res/raw/no_dirs.zip
new file mode 100644
index 0000000..e178ae1
--- /dev/null
+++ b/documents-archive/tests/res/raw/no_dirs.zip
Binary files differ
diff --git a/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
new file mode 100644
index 0000000..9845412
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.provider.tests;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.support.provider.DocumentArchive;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+/**
+ * Tests for DocumentArchive.
+ */
+public class DocumentArchiveTest extends AndroidTestCase {
+    private static final String DOCUMENT_ID = "document-id";
+    private static final char DELIMITER = ':';
+    private static final String NOTIFICATION_URI = "content://notification-uri";
+    private DocumentArchive mArchive = null;
+
+    public void loadArchive(int resource) {
+        // Extract the file from resources.
+        File file = null;
+        try {
+            file = File.createTempFile("android.support.provider.tests{",
+                    "}.zip", mContext.getCacheDir());
+            try (
+                final FileOutputStream outputStream =
+                        new ParcelFileDescriptor.AutoCloseOutputStream(
+                                ParcelFileDescriptor.open(
+                                        file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+                final InputStream inputStream =
+                        mContext.getResources().openRawResource(resource);
+            ) {
+                final byte[] buffer = new byte[32 * 1024];
+                int bytes;
+                while ((bytes = inputStream.read(buffer)) != -1) {
+                    outputStream.write(buffer, 0, bytes);
+                }
+                outputStream.flush();
+                mArchive = DocumentArchive.createForLocalFile(
+                      mContext,
+                      file,
+                      DOCUMENT_ID,
+                      DELIMITER,
+                      Uri.parse(NOTIFICATION_URI));
+
+            }
+        } catch (IOException e) {
+            fail(String.valueOf(e));
+        } finally {
+            // On UNIX the file will be still available for processes which opened it, even
+            // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+            if (file != null) {
+                file.delete();
+            }
+        }
+    }
+
+    @Override
+    public void tearDown() {
+        if (mArchive != null) {
+            mArchive.close();
+        }
+    }
+
+    public void testQueryChildDocument() throws IOException {
+        loadArchive(R.raw.archive);
+        final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+        assertTrue(cursor.moveToFirst());
+        assertEquals("document-id:dir1/",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir1",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+        assertTrue(cursor.moveToNext());
+        assertEquals("document-id:dir2/",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir2",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+        assertTrue(cursor.moveToNext());
+        assertEquals("document-id:file1.txt",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("file1.txt",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals("text/plain",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(13,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+        assertFalse(cursor.moveToNext());
+
+        // Check if querying children works too.
+        final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+        assertTrue(childCursor.moveToFirst());
+        assertEquals("document-id:dir1/cherries.txt",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("cherries.txt",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DISPLAY_NAME)));
+        assertEquals("text/plain",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_MIME_TYPE)));
+        assertEquals(17,
+                childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+    }
+
+    public void testQueryChildDocument_NoDirs() throws IOException {
+        loadArchive(R.raw.no_dirs);
+        final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+        assertTrue(cursor.moveToFirst());
+        assertEquals("document-id:dir1/",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir1",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+        assertFalse(cursor.moveToNext());
+
+        final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+        assertTrue(childCursor.moveToFirst());
+        assertEquals("document-id:dir1/dir2/",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir2",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+        assertFalse(childCursor.moveToNext());
+
+        final Cursor childCursor2 = mArchive.queryChildDocuments(
+                "document-id:dir1/dir2/", null, null);
+
+        assertTrue(childCursor2.moveToFirst());
+        assertEquals("document-id:dir1/dir2/cherries.txt",
+                childCursor2.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DOCUMENT_ID)));
+        assertFalse(childCursor2.moveToNext());
+    }
+
+    public void testQueryChildDocument_EmptyDirs() throws IOException {
+        loadArchive(R.raw.empty_dirs);
+        final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+        assertTrue(cursor.moveToFirst());
+        assertEquals("document-id:dir1/",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir1",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+        assertFalse(cursor.moveToNext());
+
+        final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+        assertTrue(childCursor.moveToFirst());
+        assertEquals("document-id:dir1/dir2/",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir2",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+        assertTrue(childCursor.moveToNext());
+        assertEquals("document-id:dir1/dir3/",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("dir3",
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_DISPLAY_NAME)));
+        assertEquals(Document.MIME_TYPE_DIR,
+                childCursor.getString(childCursor.getColumnIndexOrThrow(
+                        Document.COLUMN_MIME_TYPE)));
+        assertEquals(0,
+                childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+        assertFalse(cursor.moveToNext());
+
+        final Cursor childCursor2 = mArchive.queryChildDocuments(
+                "document-id:dir1/dir2/", null, null);
+        assertFalse(childCursor2.moveToFirst());
+
+        final Cursor childCursor3 = mArchive.queryChildDocuments(
+                "document-id:dir1/dir3/", null, null);
+        assertFalse(childCursor3.moveToFirst());
+    }
+
+    public void testGetDocumentType() throws IOException {
+        loadArchive(R.raw.archive);
+        assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType("document-id:dir1/"));
+        assertEquals("text/plain", mArchive.getDocumentType("document-id:file1.txt"));
+    }
+
+    public void testIsChildDocument() throws IOException {
+        loadArchive(R.raw.archive);
+        assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1/"));
+        assertFalse(mArchive.isChildDocument(DOCUMENT_ID, "document-id:this-does-not-exist"));
+        assertTrue(mArchive.isChildDocument("document-id:dir1/", "document-id:dir1/cherries.txt"));
+        assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1/cherries.txt"));
+    }
+
+    public void testQueryDocument() throws IOException {
+        loadArchive(R.raw.archive);
+        final Cursor cursor = mArchive.queryDocument("document-id:dir2/strawberries.txt", null);
+
+        assertTrue(cursor.moveToFirst());
+        assertEquals("document-id:dir2/strawberries.txt",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+        assertEquals("strawberries.txt",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+        assertEquals("text/plain",
+                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+        assertEquals(21,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+    }
+
+    public void testOpenDocument() throws IOException {
+        loadArchive(R.raw.archive);
+        final ParcelFileDescriptor descriptor = mArchive.openDocument(
+                "document-id:dir2/strawberries.txt", "r", null /* signal */);
+        try (final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+                new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {
+            assertEquals("I love strawberries!", new Scanner(inputStream).nextLine());
+        }
+    }
+}
diff --git a/documents-archive/tests/src/android/support/provider/IntegrationTest.java b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
new file mode 100644
index 0000000..0445d82
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.provider.tests;
+
+import android.content.ContentProviderClient;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.IllegalArgumentException;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Integration tests for DocumentsProvider and DocumentArchiveHelper.
+ *
+ * <p>Only checks if the provider, then helper are forwarding the calls to the
+ * underlying {@code ArchiveDocument} correctly. More detailed output testing is
+ * done in {@code DocumentArchiveTest}.
+ */
+public class IntegrationTest extends AndroidTestCase {
+    private ContentProviderClient mClient;
+
+    @Override
+    public void setUp() throws RemoteException {
+        mClient = getContext().getContentResolver().acquireContentProviderClient(
+                StubProvider.AUTHORITY);
+        assertNotNull(mClient);
+        mClient.call("reset", null, null);
+    }
+
+    @Override
+    public void tearDown() {
+        if (mClient != null) {
+            mClient.release();
+            mClient = null;
+        }
+    }
+
+    public void testQueryForChildren() throws IOException {
+        final Cursor cursor = mContext.getContentResolver().query(
+                DocumentsContract.buildChildDocumentsUri(
+                        StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+                        null, null, null, null);
+        assertEquals(3, cursor.getCount());
+    }
+
+    public void testQueryForDocument_Archive()
+            throws IOException, RemoteException, InterruptedException {
+        final Cursor cursor = mContext.getContentResolver().query(
+                DocumentsContract.buildDocumentUri(
+                        StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+                        null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToFirst());
+        assertEquals(Document.FLAG_ARCHIVE,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS)));
+    }
+
+    public void testQueryForDocument_ArchiveDescendant()
+            throws IOException, RemoteException, InterruptedException {
+        final Cursor cursor = mContext.getContentResolver().query(
+                DocumentsContract.buildDocumentUri(
+                        StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+                        null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        assertEquals(StubProvider.NOTIFY_URI, cursor.getNotificationUri());
+
+        final CountDownLatch changeSignal = new CountDownLatch(1);
+        final ContentObserver observer = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange) {
+                changeSignal.countDown();
+            }
+        };
+
+        try {
+            getContext().getContentResolver().registerContentObserver(
+                    cursor.getNotificationUri(), false /* notifyForDescendants */, observer);
+
+            // Simulate deleting the archive file, then confirm that the notification is
+            // propagated and the archive closed.
+            mClient.call("delete", null, null);
+            changeSignal.await();
+
+            mContext.getContentResolver().query(
+                    DocumentsContract.buildChildDocumentsUri(
+                            StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+                            null, null, null, null);
+            fail("Expected IllegalStateException, but succeeded.");
+        } catch (IllegalStateException e) {
+            // Expected, as the file is gone.
+        } finally {
+            getContext().getContentResolver().unregisterContentObserver(observer);
+        }
+    }
+
+    public void testGetType() throws IOException {
+        assertEquals("text/plain", mContext.getContentResolver().getType(
+                DocumentsContract.buildDocumentUri(
+                        StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID)));
+    }
+
+    public void testOpenFileDescriptor() throws IOException {
+        final ParcelFileDescriptor descriptor = mContext.getContentResolver().openFileDescriptor(
+                DocumentsContract.buildDocumentUri(
+                        StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+                        "r", null);
+        assertNotNull(descriptor);
+    }
+}
diff --git a/documents-archive/tests/src/android/support/provider/StubProvider.java b/documents-archive/tests/src/android/support/provider/StubProvider.java
new file mode 100644
index 0000000..3f72cd2
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/StubProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.provider.tests;
+
+import android.database.Cursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.support.provider.DocumentArchiveHelper;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Stub provider for testing support for archives.
+ */
+public class StubProvider extends DocumentsProvider {
+    public static final String AUTHORITY = "android.support.provider.tests.mystubprovider";
+    public static final String DOCUMENT_ID = "document-id";
+    public static final String FILE_DOCUMENT_ID = "document-id:dir1/cherries.txt";
+    public static final Uri NOTIFY_URI = DocumentsContract.buildRootsUri(AUTHORITY);
+
+    private static final String TAG = "StubProvider";
+    private static final String[] DEFAULT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_SIZE,
+            Document.COLUMN_MIME_TYPE, Document.COLUMN_FLAGS,
+            DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH
+    };
+
+    public File file;
+    public DocumentArchiveHelper archiveHelper;
+    public boolean simulatedDelete = false;
+
+    @Override
+    public Bundle call(String method, String args, Bundle extras) {
+        switch (method) {
+            case "reset":
+                simulatedDelete = false;
+                getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+                return null;
+            case "delete":
+                simulatedDelete = true;
+                getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+                return null;
+            default:
+                return super.call(method, args, extras);
+        }
+    }
+
+    @Override
+    public boolean onCreate() {
+        try {
+            archiveHelper = new DocumentArchiveHelper(this, ':');
+            file = TestUtils.createFileFromResource(getContext(), R.raw.archive);
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to initialize StubProvider.");
+            return false;
+        }
+    }
+
+    @Override
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        if (archiveHelper.isArchivedDocument(documentId)) {
+            return archiveHelper.openDocument(documentId, mode, signal);
+        }
+
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        if (archiveHelper.isArchivedDocument(parentDocumentId) ||
+                archiveHelper.isSupportedArchiveType(getDocumentType(parentDocumentId))) {
+            return archiveHelper.queryChildDocuments(parentDocumentId, projection, sortOrder);
+        }
+
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        if (archiveHelper.isArchivedDocument(documentId)) {
+            return archiveHelper.queryDocument(documentId, projection);
+        }
+
+        if (DOCUMENT_ID.equals(documentId)) {
+            if (simulatedDelete) {
+                throw new FileNotFoundException();
+            }
+
+            final MatrixCursor result = new MatrixCursor(
+                    projection != null ? projection : DEFAULT_PROJECTION);
+            result.setNotificationUri(getContext().getContentResolver(), NOTIFY_URI);
+            final RowBuilder row = result.newRow();
+            row.add(Document.COLUMN_DOCUMENT_ID, DOCUMENT_ID);
+            row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+            row.add(Document.COLUMN_SIZE, file.length());
+            row.add(Document.COLUMN_MIME_TYPE, "application/zip");
+            final int flags = archiveHelper.isSupportedArchiveType("application/zip")
+                    ? Document.FLAG_ARCHIVE : 0;
+            row.add(Document.COLUMN_FLAGS, flags);
+            row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, file.getPath());
+            return result;
+        }
+
+        throw new FileNotFoundException();
+    }
+
+    @Override
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        if (archiveHelper.isArchivedDocument(documentId)) {
+            return archiveHelper.getDocumentType(documentId);
+        }
+
+        if (DOCUMENT_ID.equals(documentId)) {
+            return "application/zip";
+        }
+
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/documents-archive/tests/src/android/support/provider/TestUtils.java b/documents-archive/tests/src/android/support/provider/TestUtils.java
new file mode 100644
index 0000000..17ec3e1
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/TestUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.provider.tests;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utilities for tests.
+ */
+final class TestUtils {
+    /**
+     * Saves a file from resources to a temporary location and returns a File instance for it.
+     *
+     * @param id Resource ID
+     */
+    static File createFileFromResource(Context context, int id) throws IOException {
+        final File file = File.createTempFile("android.support.provider.tests{",
+                "}.zip", context.getCacheDir());
+        try (
+            final FileOutputStream outputStream =
+                    new ParcelFileDescriptor.AutoCloseOutputStream(
+                            ParcelFileDescriptor.open(
+                                    file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+            final InputStream inputStream = context.getResources().openRawResource(id);
+        ) {
+            final byte[] buffer = new byte[32 * 1024];
+            int bytes;
+            while ((bytes = inputStream.read(buffer)) != -1) {
+                outputStream.write(buffer, 0, bytes);
+            }
+            outputStream.flush();
+            return file;
+        }
+    }
+}
diff --git a/graphics/drawable/Android.mk b/graphics/drawable/Android.mk
index c376062..2aa2767 100644
--- a/graphics/drawable/Android.mk
+++ b/graphics/drawable/Android.mk
@@ -32,6 +32,7 @@
 
 LOCAL_JAVA_LIBRARIES := android-support-v4
 
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Static API Check
@@ -62,12 +63,14 @@
 LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-vectordrawable
 
 LOCAL_AAPT_FLAGS := --no-version-vectors
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Animated API Check
 support_module := $(LOCAL_MODULE)
 support_module_api_dir := $(LOCAL_PATH)/animated/api
-support_module_src_files := $(LOCAL_SRC_FILES)
+support_module_src_files := $(LOCAL_SRC_FILES) \
+    static/src/android/support/graphics/drawable/VectorDrawableCommon.java
 support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
 support_module_java_packages := android.support.graphics.drawable
 include $(SUPPORT_API_CHECK)
diff --git a/graphics/drawable/animated/AndroidManifest.xml b/graphics/drawable/animated/AndroidManifest.xml
index 17e9ce5..98f9e17 100644
--- a/graphics/drawable/animated/AndroidManifest.xml
+++ b/graphics/drawable/animated/AndroidManifest.xml
@@ -1,4 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.graphics.drawable.animated">
     <application/>
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index c3698b8..02bb5f6 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -4,6 +4,12 @@
 
 dependencies {
     compile project(':support-vector-drawable')
+    androidTestCompile ('com.android.support.test:runner:0.4.1') {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+        exclude module: 'support-annotations'
+    }
     testCompile 'junit:junit:4.12'
 }
 
@@ -11,6 +17,7 @@
     compileSdkVersion 23
     defaultConfig {
         minSdkVersion 11
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         // This disables the builds tools automatic vector -> PNG generation
         generatedDensities = []
     }
diff --git a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
index ef98851..a8b3230 100644
--- a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -50,8 +50,8 @@
 import java.util.List;
 
 /**
- * For API 21 and above, this class is delegating to the framework's {@link AnimatedVectorDrawable}.
- * For API 20 and below, this class uses {@link android.animation.ObjectAnimator} and
+ * For API 23 and above, this class is delegating to the framework's {@link AnimatedVectorDrawable}.
+ * For older API version, this class uses {@link android.animation.ObjectAnimator} and
  * {@link android.animation.AnimatorSet} to animate the properties of a
  * {@link VectorDrawableCompat} to create an animated drawable.
  * <p>
@@ -118,7 +118,7 @@
     @Nullable
     public static AnimatedVectorDrawableCompat create(@NonNull Context context,
                                                       @DrawableRes int resId) {
-        if (Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 23) {
             final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
             drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
                     context.getTheme());
@@ -165,7 +165,7 @@
 
     /**
      * {@inheritDoc}
-     * <strong>Note</strong> that we don't support constant state when SDK < 21.
+     * <strong>Note</strong> that we don't support constant state when SDK < 23.
      * Make sure you check the return value before using it.
      */
     @Override
@@ -504,12 +504,12 @@
 
         @Override
         public Drawable newDrawable() {
-            throw new IllegalStateException("No constant state support for SDK < 21.");
+            throw new IllegalStateException("No constant state support for SDK < 23.");
         }
 
         @Override
         public Drawable newDrawable(Resources res) {
-            throw new IllegalStateException("No constant state support for SDK < 21.");
+            throw new IllegalStateException("No constant state support for SDK < 23.");
         }
 
         @Override
diff --git a/graphics/drawable/animated/tests/AndroidManifest.xml b/graphics/drawable/animated/tests/AndroidManifest.xml
index f317b13..8999852 100644
--- a/graphics/drawable/animated/tests/AndroidManifest.xml
+++ b/graphics/drawable/animated/tests/AndroidManifest.xml
@@ -1,8 +1,34 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.graphics.drawable.animated.tests">
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.support.graphics.drawable.animated.test">
+    <uses-sdk
+            android:minSdkVersion="11"
+            android:targetSdkVersion="23"
+            tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+                    android.support.test.espresso, android.support.test.espresso.idling" />
+
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application>
         <activity android:name="android.support.graphics.drawable.tests.DrawableStubActivity"/>
     </application>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.graphics.drawable.animated.test" />
 </manifest>
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
index d01d868..5318653 100644
--- a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
@@ -16,21 +16,26 @@
 
 package android.support.graphics.drawable.tests;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.support.annotation.UiThread;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
 import android.graphics.drawable.Drawable.ConstantState;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.annotation.DrawableRes;
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
+import android.support.graphics.drawable.animated.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
-
-import android.support.graphics.drawable.animated.test.R;
-
 import android.view.View;
 import android.widget.ImageButton;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -40,34 +45,37 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class AnimatedVectorDrawableTest extends ActivityInstrumentationTestCase2<DrawableStubActivity> {
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+public class AnimatedVectorDrawableTest {
+    @Rule public final ActivityTestRule<DrawableStubActivity> mActivityTestRule;
     private static final String LOGTAG = AnimatedVectorDrawableTest.class.getSimpleName();
 
     private static final int IMAGE_WIDTH = 64;
     private static final int IMAGE_HEIGHT = 64;
+    private static final @DrawableRes int DRAWABLE_RES_ID =
+            R.drawable.animation_vector_drawable_grouping_1;
 
-    private DrawableStubActivity mActivity;
+    private Context mContext;
     private Resources mResources;
     private AnimatedVectorDrawableCompat mAnimatedVectorDrawable;
     private Bitmap mBitmap;
     private Canvas mCanvas;
     private static final boolean DBG_DUMP_PNG = false;
-    private int mResId = R.drawable.animation_vector_drawable_grouping_1;
 
     public AnimatedVectorDrawableTest() {
-        super(DrawableStubActivity.class);
+        mActivityTestRule = new ActivityTestRule<>(DrawableStubActivity.class);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setup() throws Exception {
         mBitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888);
         mCanvas = new Canvas(mBitmap);
-        mActivity = getActivity();
-        mResources = mActivity.getResources();
+        mContext = mActivityTestRule.getActivity();
+        mResources = mContext.getResources();
 
-        mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mActivity, mResId);
+        mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
     }
 
     // This is only for debugging or golden image (re)generation purpose.
@@ -102,10 +110,10 @@
         }
     }
 
-    @UiThread
+    @Test
     public void testInflate() throws Exception {
         // Setup AnimatedVectorDrawableCompat from xml file
-        XmlPullParser parser = mResources.getXml(mResId);
+        XmlPullParser parser = mResources.getXml(DRAWABLE_RES_ID);
         AttributeSet attrs = Xml.asAttributeSet(parser);
 
         int type;
@@ -128,50 +136,52 @@
         assertTrue(earthColor == 0xFF5656EA);
 
         if (DBG_DUMP_PNG) {
-            saveVectorDrawableIntoPNG(mBitmap, mResId);
+            saveVectorDrawableIntoPNG(mBitmap, DRAWABLE_RES_ID);
         }
     }
 
+    @Test
     public void testGetChangingConfigurations() {
-        AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity, mResId);
-        ConstantState constantState = avd.getConstantState();
+        ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
 
         if (constantState != null) {
             // default
             assertEquals(0, constantState.getChangingConfigurations());
-            assertEquals(0, avd.getChangingConfigurations());
+            assertEquals(0, mAnimatedVectorDrawable.getChangingConfigurations());
 
             // change the drawable's configuration does not affect the state's configuration
-            avd.setChangingConfigurations(0xff);
-            assertEquals(0xff, avd.getChangingConfigurations());
+            mAnimatedVectorDrawable.setChangingConfigurations(0xff);
+            assertEquals(0xff, mAnimatedVectorDrawable.getChangingConfigurations());
             assertEquals(0, constantState.getChangingConfigurations());
 
             // the state's configuration get refreshed
-            constantState = avd.getConstantState();
+            constantState = mAnimatedVectorDrawable.getConstantState();
             assertEquals(0xff, constantState.getChangingConfigurations());
 
             // set a new configuration to drawable
-            avd.setChangingConfigurations(0xff00);
+            mAnimatedVectorDrawable.setChangingConfigurations(0xff00);
             assertEquals(0xff, constantState.getChangingConfigurations());
-            assertEquals(0xffff, avd.getChangingConfigurations());
+            assertEquals(0xffff, mAnimatedVectorDrawable.getChangingConfigurations());
         }
     }
 
-    public void testGetConstantState() throws InterruptedException, Throwable {
-        AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity, mResId);
-        ConstantState constantState = avd.getConstantState();
+    @Test
+    public void testGetConstantState() {
+        ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
         if (constantState != null) {
             assertEquals(0, constantState.getChangingConfigurations());
 
-            avd.setChangingConfigurations(1);
-            constantState = avd.getConstantState();
+            mAnimatedVectorDrawable.setChangingConfigurations(1);
+            constantState = mAnimatedVectorDrawable.getConstantState();
             assertNotNull(constantState);
             assertEquals(1, constantState.getChangingConfigurations());
         }
     }
 
-    public void testAnimateColor() throws InterruptedException, Throwable {
-        final ImageButton imageButton = (ImageButton) getActivity().findViewById(R.id.imageButton);
+    @Test
+    public void testAnimateColor() throws Throwable {
+        final ImageButton imageButton =
+                (ImageButton) mActivityTestRule.getActivity().findViewById(R.id.imageButton);
         final int viewW = imageButton.getWidth();
         final int viewH = imageButton.getHeight();
         int pixelX = viewW / 2;
@@ -181,10 +191,11 @@
                 Bitmap.Config.ARGB_8888);
         final Canvas c = new Canvas(bitmap);
         CountDownLatch latch = new CountDownLatch(numTests);
-        runTestOnUiThread(new Runnable() {
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mActivity,
+                AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mContext,
                         R.drawable.animated_color_fill);
                 imageButton.setBackgroundDrawable(avd);
                 avd.start();
@@ -207,7 +218,7 @@
      */
     private void verifyRedOnly(final int pixelX, final int pixelY, final View button,
             final Bitmap bitmap, final Canvas canvas, final CountDownLatch latch) throws Throwable {
-        runTestOnUiThread(new Runnable() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 button.draw(canvas);
@@ -221,10 +232,14 @@
         });
     }
 
+    @Test
     public void testMutate() {
-        AnimatedVectorDrawableCompat d1 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
-        AnimatedVectorDrawableCompat d2 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
-        AnimatedVectorDrawableCompat d3 = AnimatedVectorDrawableCompat.create(mActivity, mResId);
+        AnimatedVectorDrawableCompat d1 =
+                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+        AnimatedVectorDrawableCompat d2 =
+                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+        AnimatedVectorDrawableCompat d3 =
+                AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
 
         if (d1.getConstantState() != null) {
             int originalAlpha = d2.getAlpha();
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
index 1a47250..0cd197e 100644
--- a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
@@ -17,16 +17,24 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-
 import android.support.graphics.drawable.animated.test.R;
+import android.view.WindowManager;
 
 public class DrawableStubActivity extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         setContentView(R.layout.avd_layout);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
 }
 
 
diff --git a/graphics/drawable/static/AndroidManifest.xml b/graphics/drawable/static/AndroidManifest.xml
index c170289..e91290d 100644
--- a/graphics/drawable/static/AndroidManifest.xml
+++ b/graphics/drawable/static/AndroidManifest.xml
@@ -1,5 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.graphics.drawable">
+<!--
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<manifest package="android.support.graphics.drawable">
     <application/>
 </manifest>
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index f0a363f..f7a5b97 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -4,6 +4,12 @@
 
 dependencies {
     compile project(':support-v4')
+    androidTestCompile ('com.android.support.test:runner:0.4.1') {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+        exclude module: 'support-annotations'
+    }
     testCompile 'junit:junit:4.12'
 }
 
@@ -12,6 +18,7 @@
 
     defaultConfig {
         minSdkVersion 7
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         // This disables the builds tools automatic vector -> PNG generation
         generatedDensities = []
     }
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index f0d0a24..0867802 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -54,8 +54,8 @@
 import java.util.Stack;
 
 /**
- * For API 21 and above, this class is delegating to the framework's {@link VectorDrawable}.
- * For API 20 and below, this class lets you create a drawable based on an XML vector graphic.
+ * For API 23 and above, this class is delegating to the framework's {@link VectorDrawable}.
+ * For older API version, this class lets you create a drawable based on an XML vector graphic.
  * <p>
  * VectorDrawableCompat are defined in the same XML format as {@link VectorDrawable}.
  * </p>
@@ -396,7 +396,7 @@
     @Nullable
     public static VectorDrawableCompat create(@NonNull Resources res, @DrawableRes int resId,
                                               @Nullable Theme theme) {
-        if (Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 23) {
             final VectorDrawableCompat drawable = new VectorDrawableCompat();
             drawable.mDelegateDrawable = ResourcesCompat.getDrawable(res, resId, theme);
             drawable.mCachedConstantStateDelegate = new VectorDrawableDelegateState(
@@ -725,7 +725,7 @@
     }
 
     /**
-     * Constant state for delegating the creating drawable job for SDK >= 21.
+     * Constant state for delegating the creating drawable job for SDK >= 23.
      * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
      * a delegated VectorDrawable instance.
      */
diff --git a/graphics/drawable/static/tests/AndroidManifest.xml b/graphics/drawable/static/tests/AndroidManifest.xml
index 97e74c6..27f3fbd 100644
--- a/graphics/drawable/static/tests/AndroidManifest.xml
+++ b/graphics/drawable/static/tests/AndroidManifest.xml
@@ -1,6 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.graphics.drawable.tests">
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.support.graphics.drawable.test">
+    <uses-sdk
+            android:minSdkVersion="8"
+            android:targetSdkVersion="23"
+            tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+                android.support.test.espresso, android.support.test.espresso.idling" />
+
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application/>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.graphics.drawable.test" />
 </manifest>
diff --git a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
index e4b903c..8a80bd1 100644
--- a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
+++ b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
@@ -16,6 +16,7 @@
 
 package android.support.graphics.drawable.tests;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.graphics.Bitmap;
@@ -25,17 +26,24 @@
 import android.graphics.drawable.Drawable;
 import android.support.graphics.drawable.VectorDrawableCompat;
 import android.support.graphics.drawable.test.R;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
 @MediumTest
-public class VectorDrawableTest extends AndroidTestCase {
+public class VectorDrawableTest {
     private static final String LOGTAG = "VectorDrawableTest";
 
     private static final int[] ICON_RES_IDS = new int[]{
@@ -102,30 +110,31 @@
 
     private static final boolean DBG_DUMP_PNG = false;
 
+    private Context mContext;
     private Resources mResources;
     private VectorDrawableCompat mVectorDrawable;
     private Bitmap mBitmap;
     private Canvas mCanvas;
     private Theme mTheme;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setup() {
         final int width = IMAGE_WIDTH;
         final int height = IMAGE_HEIGHT;
 
         mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         mCanvas = new Canvas(mBitmap);
 
+        mContext = InstrumentationRegistry.getContext();
         mResources = mContext.getResources();
         mTheme = mContext.getTheme();
     }
 
-    public void testSimpleVectorDrawables() throws XmlPullParserException, IOException {
+    @Test
+    public void testSimpleVectorDrawables() throws Exception {
         verifyVectorDrawables(ICON_RES_IDS, GOLDEN_IMAGES, null);
     }
 
-
     private void verifyVectorDrawables(int[] resIds, int[] goldenImages, int[] stateSet)
             throws XmlPullParserException, IOException {
         for (int i = 0; i < resIds.length; i++) {
@@ -198,12 +207,11 @@
             return "";
         }
 
-        final Resources res = getContext().getResources();
         final StringBuilder builder = new StringBuilder();
         for (int i = 0; i < stateSet.length; i++) {
             builder.append('_');
 
-            final String state = res.getResourceName(stateSet[i]);
+            final String state = mResources.getResourceName(stateSet[i]);
             final int stateIndex = state.indexOf("state_");
             if (stateIndex >= 0) {
                 builder.append(state.substring(stateIndex + 6));
@@ -252,6 +260,7 @@
 
     }
 
+    @Test
     public void testGetChangingConfigurations() {
         VectorDrawableCompat vectorDrawable =
                 VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
@@ -276,6 +285,7 @@
         assertEquals(0xffff, vectorDrawable.getChangingConfigurations());
     }
 
+    @Test
     public void testGetConstantState() {
         VectorDrawableCompat vectorDrawable =
                 VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
@@ -289,8 +299,8 @@
         assertEquals(1, constantState.getChangingConfigurations());
     }
 
+    @Test
     public void testMutate() {
-        Resources resources = mContext.getResources();
         VectorDrawableCompat d1 =
                 VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
         VectorDrawableCompat d2 =
diff --git a/percent/Android.mk b/percent/Android.mk
index 5c5129e..6e04560 100644
--- a/percent/Android.mk
+++ b/percent/Android.mk
@@ -24,6 +24,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Here is the final static library that apps can link against.
@@ -36,6 +37,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_JAVA_LIBRARIES := android-support-percent-res \
     android-support-v4
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index 956b428..2416f88 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -72,6 +72,9 @@
 public class PercentLayoutHelper {
     private static final String TAG = "PercentLayout";
 
+    private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
+
     private final ViewGroup mHost;
 
     public PercentLayoutHelper(ViewGroup host) {
@@ -96,7 +99,7 @@
      * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
      */
     public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
+        if (DEBUG) {
             Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: "
                     + View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: "
                     + View.MeasureSpec.toString(heightMeasureSpec));
@@ -107,13 +110,13 @@
         for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
             View view = mHost.getChildAt(i);
             ViewGroup.LayoutParams params = view.getLayoutParams();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (DEBUG) {
                 Log.d(TAG, "should adjust " + view + " " + params);
             }
             if (params instanceof PercentLayoutParams) {
                 PercentLayoutInfo info =
                         ((PercentLayoutParams) params).getPercentLayoutInfo();
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                if (DEBUG) {
                     Log.d(TAG, "using " + info);
                 }
                 if (info != null) {
@@ -139,7 +142,7 @@
         float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent width: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -147,7 +150,7 @@
         }
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent height: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -155,7 +158,7 @@
         }
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -167,7 +170,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent left margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -176,7 +179,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent top margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -185,7 +188,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent right margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -194,7 +197,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent bottom margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -203,7 +206,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent start margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -212,7 +215,7 @@
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
                 -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "percent end margin: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -221,7 +224,7 @@
 
         value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f);
         if (value != -1f) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            if (VERBOSE) {
                 Log.v(TAG, "aspect ratio: " + value);
             }
             info = info != null ? info : new PercentLayoutInfo();
@@ -229,7 +232,7 @@
         }
 
         array.recycle();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
+        if (DEBUG) {
             Log.d(TAG, "constructed: " + info);
         }
         return info;
@@ -244,13 +247,13 @@
         for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
             View view = mHost.getChildAt(i);
             ViewGroup.LayoutParams params = view.getLayoutParams();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (DEBUG) {
                 Log.d(TAG, "should restore " + view + " " + params);
             }
             if (params instanceof PercentLayoutParams) {
                 PercentLayoutInfo info =
                         ((PercentLayoutParams) params).getPercentLayoutInfo();
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                if (DEBUG) {
                     Log.d(TAG, "using " + info);
                 }
                 if (info != null) {
@@ -283,7 +286,7 @@
         for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
             View view = mHost.getChildAt(i);
             ViewGroup.LayoutParams params = view.getLayoutParams();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (DEBUG) {
                 Log.d(TAG, "should handle measured state too small " + view + " " + params);
             }
             if (params instanceof PercentLayoutParams) {
@@ -301,7 +304,7 @@
                 }
             }
         }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
+        if (DEBUG) {
             Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);
         }
         return needsSecondMeasure;
@@ -411,7 +414,7 @@
                 }
             }
 
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (DEBUG) {
                 Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
             }
         }
@@ -474,7 +477,7 @@
                 MarginLayoutParamsCompat.resolveLayoutDirection(params,
                         ViewCompat.getLayoutDirection(view));
             }
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
+            if (DEBUG) {
                 Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height
                         + ")");
             }
diff --git a/percent/tests/java/android/support/percent/BaseTestActivity.java b/percent/tests/java/android/support/percent/BaseTestActivity.java
index ec7d7bb..c0c9c7d 100755
--- a/percent/tests/java/android/support/percent/BaseTestActivity.java
+++ b/percent/tests/java/android/support/percent/BaseTestActivity.java
@@ -24,6 +24,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         final int contentView = getContentViewLayoutResId();
         if (contentView > 0) {
@@ -33,6 +34,12 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
+
     protected abstract int getContentViewLayoutResId();
 
     protected void onContentViewSet() {}
diff --git a/previewsdk/Android.mk b/previewsdk/Android.mk
index f8806fa..ccd0547 100644
--- a/previewsdk/Android.mk
+++ b/previewsdk/Android.mk
@@ -29,4 +29,5 @@
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_GENERATED_SOURCES := $(previewsdk_gen_java_files)
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/recommendation/Android.mk b/recommendation/Android.mk
index 3d8edf8..d60bb2b 100644
--- a/recommendation/Android.mk
+++ b/recommendation/Android.mk
@@ -12,6 +12,7 @@
 
 LOCAL_MODULE := android-support-recommendation
 
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # ===========================================================
diff --git a/tests/Android.mk b/tests/Android.mk
index 7bd3b00..7993eed 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, java)
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4 junit-runner
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := AndroidSupportTests
 
diff --git a/v13/Android.mk b/v13/Android.mk
index 946d1f8..4feb7ff 100644
--- a/v13/Android.mk
+++ b/v13/Android.mk
@@ -22,6 +22,7 @@
 LOCAL_MODULE := android-support-v13-ics
 LOCAL_SDK_VERSION := 14
 LOCAL_SRC_FILES := $(call all-java-files-under, ics)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # A helper sub-library that makes direct use of Ice Cream Sandwich APIs.
@@ -30,6 +31,7 @@
 LOCAL_SDK_VERSION := 15
 LOCAL_SRC_FILES := $(call all-java-files-under, ics-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-ics
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # A helper sub-library that makes direct use of MNC APIs.
@@ -38,6 +40,16 @@
 LOCAL_SDK_VERSION := 23
 LOCAL_SRC_FILES := $(call all-java-files-under, api23)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-ics-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of NYC APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v13-nyc
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-mnc
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # -----------------------------------------------------------------------
@@ -47,8 +59,8 @@
 LOCAL_SDK_VERSION := 13
 LOCAL_SRC_FILES := $(call all-java-files-under, java)
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \
-        android-support-v13-ics-mr1 \
-        android-support-v13-mnc
+        android-support-v13-nyc
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 36ea6c0..601b4d2 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -1,5 +1,9 @@
 package android.support.v13.app {
 
+  public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+    method public static android.support.v13.view.DropPermissionsCompat requestDropPermissions(android.app.Activity, android.view.DragEvent);
+  }
+
   public class FragmentCompat {
     ctor public FragmentCompat();
     method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
@@ -36,3 +40,34 @@
 
 }
 
+package android.support.v13.view {
+
+  public abstract class DragStartHelper implements android.view.View.OnLongClickListener android.view.View.OnTouchListener {
+    ctor public DragStartHelper(android.view.View);
+    method public void attach();
+    method public android.view.View.DragShadowBuilder getShadowBuilder(android.view.View);
+    method public void getTouchPosition(android.view.View, android.graphics.Point);
+    method public boolean handleLongClick(android.view.View);
+    method public boolean handleTouch(android.view.View, android.view.MotionEvent);
+    method protected abstract boolean onDragStart(android.view.View);
+    method public boolean onLongClick(android.view.View);
+    method public boolean onTouch(android.view.View, android.view.MotionEvent);
+    method public void remove();
+  }
+
+  public class DragStartHelper.ShadowBuilder extends android.view.View.DragShadowBuilder {
+    ctor public DragStartHelper.ShadowBuilder(android.view.View);
+  }
+
+  public final class DropPermissionsCompat {
+    method public void release();
+  }
+
+  public class ViewCompat extends android.support.v4.view.ViewCompat {
+    method public static void cancelDragAndDrop(android.view.View);
+    method public static boolean startDragAndDrop(android.view.View, android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder);
+  }
+
+}
+
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v13/api24/android/support/v13/app/FragmentCompatApi24.java
similarity index 67%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v13/api24/android/support/v13/app/FragmentCompatApi24.java
index 8126b38..4baf02a 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v13/api24/android/support/v13/app/FragmentCompatApi24.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
 
-import android.app.Activity;
+package android.support.v13.app;
 
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+import android.app.Fragment;
+
+class FragmentCompatApi24 {
+    public static void setUserVisibleHint(Fragment f, boolean isVisible) {
+        f.setUserVisibleHint(isVisible);
+    }
 }
diff --git a/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java b/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java
new file mode 100644
index 0000000..47ee4d1
--- /dev/null
+++ b/v13/api24/android/support/v13/view/DropPermissionsCompatApi24.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v13.view;
+
+import android.app.Activity;
+import android.view.DragEvent;
+import android.view.DropPermissions;
+
+class DropPermissionsCompatApi24 {
+    public static Object request(Activity activity, DragEvent dragEvent) {
+        return activity.requestDropPermissions(dragEvent);
+    }
+
+    public static void release(Object dropPermissions) {
+        ((DropPermissions)dropPermissions).release();
+    }
+}
diff --git a/v13/api24/android/support/v13/view/ViewCompatApi24.java b/v13/api24/android/support/v13/view/ViewCompatApi24.java
new file mode 100644
index 0000000..725cad0
--- /dev/null
+++ b/v13/api24/android/support/v13/view/ViewCompatApi24.java
@@ -0,0 +1,37 @@
+/*
+ * 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.v13.view;
+
+import android.content.ClipData;
+import android.view.View;
+
+class ViewCompatApi24 {
+    public static boolean startDragAndDrop(View v, ClipData data,
+           View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+        return v.startDragAndDrop(data, shadowBuilder, localState, flags);
+    }
+
+    public static void cancelDragAndDrop(View v) {
+        v.cancelDragAndDrop();
+    }
+
+    public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+        v.updateDragShadow(shadowBuilder);
+    }
+
+    private ViewCompatApi24() {}
+}
diff --git a/v13/build.gradle b/v13/build.gradle
index b0d59e6..f65557a 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -11,7 +11,9 @@
 
 def icsSS          = createApiSourceset('ics',          'ics',         '14',       null)
 def icsMr1SS       = createApiSourceset('icsmr1',       'ics-mr1',     '15',       icsSS)
-def api23SS        = createApiSourceset('api23',        'api23',       'current',  icsMr1SS)
+def api23SS        = createApiSourceset('api23',        'api23',       '23',  icsMr1SS)
+def api24SS        = createApiSourceset('api24',        'api24',       'current',  api23SS)
+
 
 def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
     def sourceSet = sourceSets.create(name)
diff --git a/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java b/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
index 39f0922..ea40376 100644
--- a/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
+++ b/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
@@ -20,6 +20,8 @@
 
 class FragmentCompatICSMR1 {
     public static void setUserVisibleHint(Fragment f, boolean isVisible) {
-        f.setUserVisibleHint(isVisible);
+        if (f.getFragmentManager() != null) {
+            f.setUserVisibleHint(isVisible);
+        }
     }
 }
diff --git a/v13/java/android/support/v13/app/ActivityCompat.java b/v13/java/android/support/v13/app/ActivityCompat.java
new file mode 100644
index 0000000..a97d2ef
--- /dev/null
+++ b/v13/java/android/support/v13/app/ActivityCompat.java
@@ -0,0 +1,43 @@
+/*
+ * 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.v13.app;
+
+import android.app.Activity;
+import android.support.v13.view.DropPermissionsCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.app.Activity}
+ * introduced after API level 13 in a backwards compatible fashion.
+ */
+public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+
+    /**
+     * Create {@link DropPermissionsCompat} object bound to this activity and controlling
+     * the access permissions for content URIs associated with the {@link android.view.DragEvent}.
+     * @param dragEvent Drag event to request permission for
+     * @return The {@link DropPermissionsCompat} object used to control access to the content URIs.
+     * {@code null} if no content URIs are associated with the event or if permissions could not be
+     * granted.
+     */
+    public static DropPermissionsCompat requestDropPermissions(Activity activity,
+                                                               DragEvent dragEvent) {
+        return DropPermissionsCompat.request(activity, dragEvent);
+    }
+
+    private ActivityCompat() {}
+}
diff --git a/v13/java/android/support/v13/app/FragmentCompat.java b/v13/java/android/support/v13/app/FragmentCompat.java
index 535092b..3812a97 100644
--- a/v13/java/android/support/v13/app/FragmentCompat.java
+++ b/v13/java/android/support/v13/app/FragmentCompat.java
@@ -23,6 +23,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
 
 import java.util.Arrays;
 
@@ -101,9 +102,18 @@
         }
     }
 
+    static class NFragmentCompatImpl extends MncFragmentCompatImpl {
+        @Override
+        public void setUserVisibleHint(Fragment f, boolean deferStart) {
+            FragmentCompatApi24.setUserVisibleHint(f, deferStart);
+        }
+    }
+
     static final FragmentCompatImpl IMPL;
     static {
-        if (Build.VERSION.SDK_INT >= 23) {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new NFragmentCompatImpl();
+        } else if (Build.VERSION.SDK_INT >= 23) {
             IMPL = new MncFragmentCompatImpl();
         } else if (android.os.Build.VERSION.SDK_INT >= 15) {
             IMPL = new ICSMR1FragmentCompatImpl();
diff --git a/v13/java/android/support/v13/app/FragmentPagerAdapter.java b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
index 60aa5d0..adc08c2 100644
--- a/v13/java/android/support/v13/app/FragmentPagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
@@ -81,6 +81,10 @@
 
     @Override
     public void startUpdate(ViewGroup container) {
+        if (container.getId() == View.NO_ID) {
+            throw new IllegalStateException("ViewPager with adapter " + this
+                    + " requires a view id");
+        }
     }
 
     @Override
diff --git a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
index f62a11f..a28f6e8 100644
--- a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -87,6 +87,10 @@
 
     @Override
     public void startUpdate(ViewGroup container) {
+        if (container.getId() == View.NO_ID) {
+            throw new IllegalStateException("ViewPager with adapter " + this
+                    + " requires a view id");
+        }
     }
 
     @Override
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/v13/java/android/support/v13/view/DragStartHelper.java
new file mode 100644
index 0000000..71c4e13
--- /dev/null
+++ b/v13/java/android/support/v13/view/DragStartHelper.java
@@ -0,0 +1,186 @@
+/*
+ * 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.v13.view;
+
+
+import android.graphics.Point;
+import android.support.v4.view.InputDeviceCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * DragStartHelper is a utility class for implementing drag and drop support.
+ * <p>
+ * It detects gestures commonly used to start drag (long click for any input source,
+ * click and drag for mouse).
+ * <p>
+ * It also keeps track of the screen location where the drag started, and helps determining
+ * the hot spot position for drag shadow.
+ * <p>
+ * Implement {@link #onDragStart(View)} method to start the drag operation:
+ * <pre>
+ * mDragStartHelper = new DragStartHelper(mDraggableView) {
+ *     protected void onDragStart(View v) {
+ *         v.startDrag(mClipData, getShadowBuilder(view), mLocalState, mDragFlags);
+ *     }
+ * };
+ * </pre>
+ * Once created, DragStartHelper can be attached to a view (this will replace existing long click
+ * and touch listeners):
+ * <pre>
+ * mDragStartHelper.attach();
+ * </pre>
+ * It may also be used in combination with existing listeners:
+ * <pre>
+ * public boolean onTouch(View view, MotionEvent event) {
+ *     return mDragStartHelper.handleTouch(view, event) || handleTouchEvent(view, event);
+ * }
+ * public boolean onLongClick(View view) {
+ *     return mDragStartHelper.handleLongClick(view) || handleLongClickEvent(view);
+ * }
+ * </pre>
+ */
+public abstract class DragStartHelper implements View.OnLongClickListener, View.OnTouchListener {
+    final private View mView;
+    private float mLastTouchRawX, mLastTouchRawY;
+
+    /**
+     * Create a DragStartHelper associated with the specified view.
+     * The newly created helper is not initally attached to the view, {@link #attach} must be
+     * called explicitly.
+     * @param view A View
+     */
+    public DragStartHelper(View view) {
+        mView = view;
+    }
+
+    /**
+     * Attach the helper to the view.
+     * <p>
+     * This will replace previously existing touch and long click listeners.
+     */
+    public void attach() {
+        mView.setOnLongClickListener(this);
+        mView.setOnTouchListener(this);
+    }
+
+    /**
+     * Detach the helper from the view.
+     * <p>
+     * This will reset touch and long click listeners to {@code null}.
+     */
+    public void remove() {
+        mView.setOnLongClickListener(null);
+        mView.setOnTouchListener(null);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        return handleTouch(v, event);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        return handleLongClick(v);
+    }
+
+    /**
+     * Handle a touch event.
+     * @param v The view the touch event has been dispatched to.
+     * @param event The MotionEvent object containing full information about
+     *        the event.
+     * @return True if the listener has consumed the event, false otherwise.
+     */
+    public boolean handleTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN ||
+                event.getAction() == MotionEvent.ACTION_MOVE) {
+            mLastTouchRawX = event.getRawX();
+            mLastTouchRawY = event.getRawY();
+        }
+        if (event.getAction() == MotionEvent.ACTION_MOVE &&
+                MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE) &&
+                (MotionEventCompat.getButtonState(event) & MotionEventCompat.BUTTON_PRIMARY) != 0) {
+            return onDragStart(v);
+        }
+        return false;
+    }
+
+    /**
+     * Handle a long click event.
+     * @param v The view that was clicked and held.
+     * @return true if the callback consumed the long click, false otherwise.
+     */
+    public boolean handleLongClick(View v) {
+        return onDragStart(v);
+    }
+
+    /**
+     * Compute the position of the touch event that started the drag operation.
+     * @param v The view relative to which the position should be computed.
+     * @param point The position of the touch event that started the drag operation.
+     */
+    public void getTouchPosition(View v, Point point) {
+        int [] viewLocation = new int[2];
+        v.getLocationOnScreen(viewLocation);
+        point.set((int) mLastTouchRawX - viewLocation[0], (int) mLastTouchRawY - viewLocation[1]);
+    }
+
+    /**
+     * Create a {@link View.DragShadowBuilder} which will build a drag shadow with the same
+     * appearance and dimensions as the specified view and with the hot spot at the location of
+     * the touch event that started the drag operation.
+     * @param view A view
+     * @return  {@link View.DragShadowBuilder}
+     */
+    public View.DragShadowBuilder getShadowBuilder(View view) {
+        return new ShadowBuilder(view);
+    }
+
+    /**
+     * A utility class that builds a drag shadow with the same appearance and dimensions as the
+     * specified view and with the hot spot at the location of the touch event that started the
+     * drag operation.
+     * <p>
+     * At the start of the drag operation a drag shadow built this way will be positioned directly
+     * over the specified view.
+     */
+    public class ShadowBuilder extends View.DragShadowBuilder {
+        /**
+         * Constructs a shadow image builder based on a View. By default, the resulting drag
+         * shadow will have the same appearance and dimensions as the View, with the touch point
+         * at the location of the touch event that started the drag operation.
+         * @param view A view.
+         */
+        public ShadowBuilder(View view) {
+            super(view);
+        }
+
+        @Override
+        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+            super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+            getTouchPosition(getView(), shadowTouchPoint);
+        }
+    }
+
+    /**
+     * Called when the drag start gesture has been detected.
+     * @param v A view on which the drag start gesture has been detected.
+     */
+    protected abstract boolean onDragStart(View v);
+}
+
diff --git a/v13/java/android/support/v13/view/DropPermissionsCompat.java b/v13/java/android/support/v13/view/DropPermissionsCompat.java
new file mode 100644
index 0000000..7b6078e
--- /dev/null
+++ b/v13/java/android/support/v13/view/DropPermissionsCompat.java
@@ -0,0 +1,88 @@
+/*
+ * 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.v13.view;
+
+import android.app.Activity;
+import android.support.v4.os.BuildCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.view.DropPermissions}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public final class DropPermissionsCompat {
+
+    interface DropPermissionsCompatImpl {
+        Object request(Activity activity, DragEvent dragEvent);
+        void release(Object dropPermissions);
+    }
+
+    static class BaseDropPermissionsCompatImpl implements DropPermissionsCompatImpl {
+        @Override
+        public Object request(Activity activity, DragEvent dragEvent) {
+            return null;
+        }
+
+        @Override
+        public void release(Object dropPermissions) {
+            // no-op
+        }
+    }
+
+    static class Api24DropPermissionsCompatImpl extends BaseDropPermissionsCompatImpl {
+        @Override
+        public Object request(Activity activity, DragEvent dragEvent) {
+            return DropPermissionsCompatApi24.request(activity, dragEvent);
+        }
+
+        @Override
+        public void release(Object dropPermissions) {
+            DropPermissionsCompatApi24.release(dropPermissions);
+        }
+    }
+
+    private static DropPermissionsCompatImpl IMPL;
+    static {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new Api24DropPermissionsCompatImpl();
+        } else {
+            IMPL = new BaseDropPermissionsCompatImpl();
+        }
+    }
+
+    private Object mDropPermissions;
+
+    private DropPermissionsCompat(Object dropPermissions) {
+        mDropPermissions = dropPermissions;
+    }
+
+    /** @hide */
+    public static DropPermissionsCompat request(Activity activity, DragEvent dragEvent) {
+        Object dropPermissions = IMPL.request(activity, dragEvent);
+        if (dropPermissions != null) {
+            return new DropPermissionsCompat(dropPermissions);
+        }
+        return null;
+    }
+
+    /*
+     * Revoke the permission grant explicitly.
+     */
+    public void release() {
+        IMPL.release(mDropPermissions);
+    }
+}
diff --git a/v13/java/android/support/v13/view/ViewCompat.java b/v13/java/android/support/v13/view/ViewCompat.java
new file mode 100644
index 0000000..38db5fa
--- /dev/null
+++ b/v13/java/android/support/v13/view/ViewCompat.java
@@ -0,0 +1,105 @@
+/*
+ * 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.v13.view;
+
+import android.content.ClipData;
+import android.support.v4.os.BuildCompat;
+import android.view.View;
+
+/**
+ * Helper for accessing features in {@link View} introduced after API
+ * level 13 in a backwards compatible fashion.
+ */
+public class ViewCompat extends android.support.v4.view.ViewCompat {
+    interface ViewCompatImpl {
+        boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+                                 Object localState, int flags);
+        void cancelDragAndDrop(View v);
+        void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder);
+    }
+
+    private static class BaseViewCompatImpl implements ViewCompatImpl {
+        @Override
+        public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+                Object localState, int flags) {
+            return v.startDrag(data, shadowBuilder, localState, flags);
+        }
+
+        @Override
+        public void cancelDragAndDrop(View v) {
+            // no-op
+        }
+
+        @Override
+        public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+            // no-op
+        }
+    }
+
+    private static class Api24ViewCompatImpl implements ViewCompatImpl {
+        @Override
+        public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+                Object localState, int flags) {
+            return ViewCompatApi24.startDragAndDrop(
+                    v, data, shadowBuilder, localState, flags);
+        }
+
+        @Override
+        public void cancelDragAndDrop(View v) {
+            ViewCompatApi24.cancelDragAndDrop(v);
+        }
+
+        @Override
+        public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+            ViewCompatApi24.updateDragShadow(v, shadowBuilder);
+        }
+    }
+
+    static ViewCompatImpl IMPL;
+    static {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new Api24ViewCompatImpl();
+        } else {
+            IMPL = new BaseViewCompatImpl();
+        }
+    }
+
+    /**
+     * Start the drag and drop operation.
+     */
+    public static boolean startDragAndDrop(View v, ClipData data,
+            View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+        return IMPL.startDragAndDrop(v, data, shadowBuilder, localState, flags);
+    }
+
+    /**
+     * Cancel the drag and drop operation.
+     */
+    public static void cancelDragAndDrop(View v) {
+        IMPL.cancelDragAndDrop(v);
+    }
+
+    /**
+     * Update the drag shadow while drag and drop is in progress.
+     */
+    public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+        IMPL.updateDragShadow(v, shadowBuilder);
+    }
+
+    private ViewCompat() {
+    }
+}
diff --git a/v14/preference/Android.mk b/v14/preference/Android.mk
index e0a5243..f01c53f 100644
--- a/v14/preference/Android.mk
+++ b/v14/preference/Android.mk
@@ -20,8 +20,8 @@
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v14-preference-res
+LOCAL_SRC_FILES := $(call all-java-files-under, ../../v7/preference/constants)
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
 LOCAL_RESOURCE_DIR := \
         frameworks/support/v7/appcompat/res \
         frameworks/support/v7/preference/res \
@@ -29,6 +29,7 @@
 LOCAL_AAPT_FLAGS := \
 	--auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Here is the final static library that apps can link against.
@@ -47,6 +48,7 @@
         android-support-v7-preference \
         android-support-annotations \
         android-support-v14-preference-res
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
@@ -56,4 +58,4 @@
 support_module_src_files := $(LOCAL_SRC_FILES)
 support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
 support_module_java_packages := android.support.v14.preference
-include $(SUPPORT_API_CHECK)
\ No newline at end of file
+include $(SUPPORT_API_CHECK)
diff --git a/v14/preference/res/layout/preference_dropdown_material.xml b/v14/preference/res/layout/preference_dropdown_material.xml
new file mode 100644
index 0000000..d1ca64e
--- /dev/null
+++ b/v14/preference/res/layout/preference_dropdown_material.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:visibility="invisible" />
+
+    <include layout="@layout/preference_material" />
+
+</FrameLayout>
diff --git a/v14/preference/res/layout/preference_widget_switch.xml b/v14/preference/res/layout/preference_widget_switch.xml
index ae83afa..afc4351 100644
--- a/v14/preference/res/layout/preference_widget_switch.xml
+++ b/v14/preference/res/layout/preference_widget_switch.xml
@@ -18,7 +18,7 @@
 <!-- Layout used by SwitchPreference for the switch widget style. This is inflated
      inside android.R.layout.preference. -->
 <Switch xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/switchWidget"
+    android:id="@android:id/switch_widget"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:focusable="false"
diff --git a/v14/preference/res/values/attrs.xml b/v14/preference/res/values/attrs.xml
index 16f9699..7aa2245 100644
--- a/v14/preference/res/values/attrs.xml
+++ b/v14/preference/res/values/attrs.xml
@@ -41,18 +41,6 @@
         <attr name="android:disableDependentsState" />
     </declare-styleable>
 
-    <declare-styleable name="MultiSelectListPreference">
-        <!-- The human-readable array to present as a list. Each entry must have a corresponding
-             index in entryValues. -->
-        <attr name="entries" />
-        <attr name="android:entries" />
-        <!-- The array to find the value to save for a preference when an entry from
-             entries is selected. If a user clicks the second item in entries, the
-             second item in this array will be saved to the preference. -->
-        <attr name="entryValues" />
-        <attr name="android:entryValues" />
-    </declare-styleable>
-
     <!-- Base attributes available to PreferenceFragment. -->
     <declare-styleable name="PreferenceFragment">
         <!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->
diff --git a/v14/preference/res/values/styles.xml b/v14/preference/res/values/styles.xml
index c197121..a83417a 100644
--- a/v14/preference/res/values/styles.xml
+++ b/v14/preference/res/values/styles.xml
@@ -60,6 +60,10 @@
         <item name="android:layout">@layout/preference_material</item>
     </style>
 
+    <style name="Preference.DropDown.Material">
+        <item name="android:layout">@layout/preference_dropdown_material</item>
+    </style>
+
     <style name="Preference_TextAppearanceMaterialBody2">
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">sans-serif</item>
diff --git a/v14/preference/res/values/themes.xml b/v14/preference/res/values/themes.xml
index 64bc91e..026d2d8 100644
--- a/v14/preference/res/values/themes.xml
+++ b/v14/preference/res/values/themes.xml
@@ -33,6 +33,7 @@
         <item name="switchPreferenceStyle">@style/Preference.SwitchPreference.Material</item>
         <item name="dialogPreferenceStyle">@style/Preference.DialogPreference.Material</item>
         <item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
+        <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
     </style>
 </resources>
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 2a636fe..8316b0d 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -72,7 +72,8 @@
     }
 
     public MultiSelectListPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.dialogPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+                android.R.attr.dialogPreferenceStyle));
     }
 
     public MultiSelectListPreference(Context context) {
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
index 56868a7..52552a6 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
@@ -28,7 +28,9 @@
 import android.os.Message;
 import android.support.annotation.Nullable;
 import android.support.annotation.XmlRes;
+import android.support.v4.content.res.TypedArrayUtils;
 import android.support.v4.view.ViewCompat;
+import android.support.v7.preference.AndroidResources;
 import android.support.v7.preference.DialogPreference;
 import android.support.v7.preference.EditTextPreference;
 import android.support.v7.preference.ListPreference;
@@ -242,7 +244,8 @@
 
         TypedArray a = mStyledContext.obtainStyledAttributes(null,
                 R.styleable.PreferenceFragment,
-                R.attr.preferenceFragmentStyle,
+                TypedArrayUtils.getAttr(mStyledContext, R.attr.preferenceFragmentStyle,
+                        AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
                 0);
 
         mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId);
@@ -263,10 +266,10 @@
 
         final View view = themedInflater.inflate(mLayoutResId, container, false);
 
-        final View rawListContainer = view.findViewById(R.id.list_container);
+        final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
         if (!(rawListContainer instanceof ViewGroup)) {
-            throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
-                    + "that is not a ViewGroup class");
+            throw new RuntimeException("Content has view with id attribute "
+                    + "'android.R.id.list_container' that is not a ViewGroup class");
         }
 
         final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -315,14 +318,19 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
 
         if (mHavePrefs) {
             bindPreferences();
         }
 
         mInitDone = true;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
 
         if (savedInstanceState != null) {
             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -351,9 +359,12 @@
 
     @Override
     public void onDestroyView() {
-        mList = null;
         mHandler.removeCallbacks(mRequestFocus);
         mHandler.removeMessages(MSG_BIND_PREFERENCES);
+        if (mHavePrefs) {
+            unbindPreferences();
+        }
+        mList = null;
         super.onDestroyView();
     }
 
@@ -520,6 +531,14 @@
         onBindPreferences();
     }
 
+    private void unbindPreferences() {
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            preferenceScreen.onDetached();
+        }
+        onUnbindPreferences();
+    }
+
     /** @hide */
     protected void onBindPreferences() {
     }
@@ -642,11 +661,6 @@
             final int width = parent.getWidth();
             for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
                 final View view = parent.getChildAt(childViewIndex);
-                if (shouldDrawDividerAbove(view, parent)) {
-                    int top = (int) ViewCompat.getY(view);
-                    mDivider.setBounds(0, top, width, top + mDividerHeight);
-                    mDivider.draw(c);
-                }
                 if (shouldDrawDividerBelow(view, parent)) {
                     int top = (int) ViewCompat.getY(view) + view.getHeight();
                     mDivider.setBounds(0, top, width, top + mDividerHeight);
@@ -658,32 +672,27 @@
         @Override
         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                 RecyclerView.State state) {
-            if (shouldDrawDividerAbove(view, parent)) {
-                outRect.top = mDividerHeight;
-            }
             if (shouldDrawDividerBelow(view, parent)) {
                 outRect.bottom = mDividerHeight;
             }
         }
 
-        private boolean shouldDrawDividerAbove(View view, RecyclerView parent) {
-            final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
-            return holder.getAdapterPosition() == 0 &&
-                    ((PreferenceViewHolder) holder).isDividerAllowedAbove();
-        }
-
         private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
-            final PreferenceViewHolder holder =
-                    (PreferenceViewHolder) parent.getChildViewHolder(view);
+            final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
+            final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
+                    && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
+            if (!dividerAllowedBelow) {
+                return false;
+            }
             boolean nextAllowed = true;
             int index = parent.indexOfChild(view);
             if (index < parent.getChildCount() - 1) {
                 final View nextView = parent.getChildAt(index + 1);
-                final PreferenceViewHolder nextHolder =
-                        (PreferenceViewHolder) parent.getChildViewHolder(nextView);
-                nextAllowed = nextHolder.isDividerAllowedAbove();
+                final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
+                nextAllowed = nextHolder instanceof PreferenceViewHolder
+                        && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
             }
-            return nextAllowed && holder.isDividerAllowedBelow();
+            return nextAllowed;
         }
 
         public void setDivider(Drawable divider) {
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index 1a46cc4..ac2a9c6 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v7.preference.AndroidResources;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.support.v7.preference.TwoStatePreference;
 import android.util.AttributeSet;
@@ -121,7 +122,8 @@
      * @param attrs Style attributes that differ from the default
      */
     public SwitchPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.switchPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
+                android.R.attr.switchPreferenceStyle));
     }
 
     /**
@@ -136,7 +138,7 @@
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
-        View switchView = holder.findViewById(R.id.switchWidget);
+        View switchView = holder.findViewById(AndroidResources.ANDROID_R_SWITCH_WIDGET);
         syncSwitchView(switchView);
         syncSummaryView(holder);
     }
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index b3aa72a..d55ae61 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -26,6 +26,7 @@
 LOCAL_AAPT_FLAGS := \
         --auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -38,6 +39,7 @@
 LOCAL_MODULE := android-support-v17-leanback-common
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, common)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -50,6 +52,7 @@
 LOCAL_SDK_VERSION := 23
 LOCAL_SRC_FILES := $(call all-java-files-under, api23)
 LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -62,6 +65,7 @@
 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
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -74,6 +78,7 @@
 LOCAL_SDK_VERSION := 19
 LOCAL_SRC_FILES := $(call all-java-files-under, kitkat)
 LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -86,6 +91,7 @@
 LOCAL_SDK_VERSION := 18
 LOCAL_SRC_FILES := $(call all-java-files-under, jbmr2)
 LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -107,6 +113,7 @@
         android-support-v4 \
         android-support-v7-recyclerview \
         android-support-v17-leanback-res
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index f054a8d..25db043 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -1,5 +1,9 @@
 package android.support.v17.leanback.app {
 
+  public abstract interface Adaptable {
+    method public abstract java.lang.Object getAdapter(java.lang.Class);
+  }
+
   public final class BackgroundManager {
     method public void attach(android.view.Window);
     method public final int getColor();
@@ -20,7 +24,11 @@
     method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
     method public int getSelectedPosition();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
     method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setAlignment(int);
     method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
     method public void setSelectedPosition(int);
     method public void setSelectedPosition(int, boolean);
@@ -30,7 +38,11 @@
     method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
     method public int getSelectedPosition();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
     method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setAlignment(int);
     method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
     method public void setSelectedPosition(int);
     method public void setSelectedPosition(int, boolean);
@@ -40,11 +52,13 @@
     ctor public BrowseFragment();
     method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
     method protected java.lang.Object createEntranceTransition();
-    method public void enableRowScaling(boolean);
+    method public void enableMainFragmentScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBrandColor();
     method public android.support.v17.leanback.app.HeadersFragment getHeadersFragment();
     method public int getHeadersState();
+    method public final android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
     method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
     method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
@@ -52,6 +66,7 @@
     method public final boolean isHeadersTransitionOnBackEnabled();
     method public boolean isInHeadersTransition();
     method public boolean isShowingHeaders();
+    method public void onDestroyView();
     method protected void onEntranceTransitionEnd();
     method protected void onEntranceTransitionPrepare();
     method protected void onEntranceTransitionStart();
@@ -81,15 +96,58 @@
     method public void onHeadersTransitionStop(boolean);
   }
 
+  public static abstract class BrowseFragment.FragmentFactory {
+    ctor public BrowseFragment.FragmentFactory();
+    method public abstract T createFragment(java.lang.Object);
+  }
+
+  public static class BrowseFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseFragment.FragmentFactory {
+    ctor public BrowseFragment.ListRowFragmentFactory();
+    method public android.support.v17.leanback.app.RowsFragment createFragment(java.lang.Object);
+  }
+
+  public static class BrowseFragment.MainFragmentAdapter {
+    ctor public BrowseFragment.MainFragmentAdapter(T);
+    method public final T getFragment();
+    method public boolean isScalingEnabled();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setScalingEnabled(boolean);
+  }
+
+  public static final class BrowseFragment.MainFragmentAdapterRegistry {
+    ctor public BrowseFragment.MainFragmentAdapterRegistry();
+    method public android.app.Fragment createFragment(java.lang.Object);
+    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseFragment.FragmentFactory);
+  }
+
+  public static class BrowseFragment.MainFragmentRowsAdapter {
+    ctor public BrowseFragment.MainFragmentRowsAdapter(T);
+    method public final T getFragment();
+    method public int getSelectedPosition();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void setSelectedPosition(int, boolean);
+  }
+
   public class BrowseSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
     ctor public BrowseSupportFragment();
     method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
     method protected java.lang.Object createEntranceTransition();
-    method public void enableRowScaling(boolean);
+    method public void enableMainFragmentScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBrandColor();
     method public int getHeadersState();
     method public android.support.v17.leanback.app.HeadersSupportFragment getHeadersSupportFragment();
+    method public final android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
     method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
     method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
@@ -97,6 +155,7 @@
     method public final boolean isHeadersTransitionOnBackEnabled();
     method public boolean isInHeadersTransition();
     method public boolean isShowingHeaders();
+    method public void onDestroyView();
     method protected void onEntranceTransitionEnd();
     method protected void onEntranceTransitionPrepare();
     method protected void onEntranceTransitionStart();
@@ -126,11 +185,52 @@
     method public void onHeadersTransitionStop(boolean);
   }
 
+  public static abstract class BrowseSupportFragment.FragmentFactory {
+    ctor public BrowseSupportFragment.FragmentFactory();
+    method public abstract T createFragment(java.lang.Object);
+  }
+
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory {
+    ctor public BrowseSupportFragment.ListRowFragmentFactory();
+    method public android.support.v17.leanback.app.RowsSupportFragment createFragment(java.lang.Object);
+  }
+
+  public static class BrowseSupportFragment.MainFragmentAdapter {
+    ctor public BrowseSupportFragment.MainFragmentAdapter(T);
+    method public final T getFragment();
+    method public boolean isScalingEnabled();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setScalingEnabled(boolean);
+  }
+
+  public static final class BrowseSupportFragment.MainFragmentAdapterRegistry {
+    ctor public BrowseSupportFragment.MainFragmentAdapterRegistry();
+    method public android.support.v4.app.Fragment createFragment(java.lang.Object);
+    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory);
+  }
+
+  public static class BrowseSupportFragment.MainFragmentRowsAdapter {
+    ctor public BrowseSupportFragment.MainFragmentRowsAdapter(T);
+    method public final T getFragment();
+    method public int getSelectedPosition();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void setSelectedPosition(int, boolean);
+  }
+
   public class DetailsFragment extends android.support.v17.leanback.app.BrandedFragment {
     ctor public DetailsFragment();
     method protected java.lang.Object createEntranceTransition();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
     method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
     method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
     method protected void onEntranceTransitionEnd();
@@ -141,8 +241,8 @@
     method public void onStart();
     method protected void runEntranceTransition(java.lang.Object);
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
     method public void setSelectedPosition(int);
     method public void setSelectedPosition(int, boolean);
     method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
@@ -153,7 +253,7 @@
     ctor public DetailsSupportFragment();
     method protected java.lang.Object createEntranceTransition();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
     method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
     method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
     method protected void onEntranceTransitionEnd();
@@ -164,8 +264,8 @@
     method public void onStart();
     method protected void runEntranceTransition(java.lang.Object);
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
     method public void setSelectedPosition(int);
     method public void setSelectedPosition(int, boolean);
     method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
@@ -326,6 +426,7 @@
 
   public class HeadersFragment extends android.support.v17.leanback.app.BaseRowFragment {
     ctor public HeadersFragment();
+    method public boolean isScrolling();
     method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener);
     method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
   }
@@ -340,6 +441,7 @@
 
   public class HeadersSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
     ctor public HeadersSupportFragment();
+    method public boolean isScrolling();
     method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener);
     method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
   }
@@ -373,6 +475,42 @@
     method protected void startPlayback(int);
   }
 
+  public abstract class OnboardingFragment extends android.app.Fragment {
+    ctor public OnboardingFragment();
+    method protected final int getCurrentPageIndex();
+    method public final int getLogoResourceId();
+    method protected abstract int getPageCount();
+    method protected abstract java.lang.CharSequence getPageDescription(int);
+    method protected abstract java.lang.CharSequence getPageTitle(int);
+    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateEnterAnimation();
+    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateLogoAnimation();
+    method protected void onFinishFragment();
+    method protected void onPageChanged(int, int);
+    method public int onProvideTheme();
+    method public final void setLogoResourceId(int);
+  }
+
+  public abstract class OnboardingSupportFragment extends android.support.v4.app.Fragment {
+    ctor public OnboardingSupportFragment();
+    method protected final int getCurrentPageIndex();
+    method public final int getLogoResourceId();
+    method protected abstract int getPageCount();
+    method protected abstract java.lang.CharSequence getPageDescription(int);
+    method protected abstract java.lang.CharSequence getPageTitle(int);
+    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateEnterAnimation();
+    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateLogoAnimation();
+    method protected void onFinishFragment();
+    method protected void onPageChanged(int, int);
+    method public int onProvideTheme();
+    method public final void setLogoResourceId(int);
+  }
+
   public abstract class PlaybackControlGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
     ctor public PlaybackControlGlue(android.content.Context, int[]);
     ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
@@ -485,6 +623,7 @@
 
   public class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
     ctor public PlaybackOverlayFragment();
+    method public void fadeOut();
     method public int getBackgroundType();
     method public android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public final android.support.v17.leanback.app.PlaybackOverlayFragment.InputEventHandler getInputEventHandler();
@@ -513,6 +652,7 @@
 
   public class PlaybackOverlaySupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
     ctor public PlaybackOverlaySupportFragment();
+    method public void fadeOut();
     method public int getBackgroundType();
     method public android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public final android.support.v17.leanback.app.PlaybackOverlaySupportFragment.InputEventHandler getInputEventHandler();
@@ -539,32 +679,54 @@
     method public void onFadeOutComplete();
   }
 
-  public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment {
+  public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment implements android.support.v17.leanback.app.Adaptable {
     ctor public RowsFragment();
-    method public void enableRowScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
     method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public java.lang.Object getAdapter(java.lang.Class);
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+    method public boolean isScrolling();
+    method public void setEntranceTransitionState(boolean);
     method public void setExpand(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
     method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
   }
 
-  public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
+  public static class RowsFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter {
+    ctor public RowsFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsFragment);
+  }
+
+  public static class RowsFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter {
+    ctor public RowsFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsFragment);
+  }
+
+  public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment implements android.support.v17.leanback.app.Adaptable {
     ctor public RowsSupportFragment();
-    method public void enableRowScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
     method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public java.lang.Object getAdapter(java.lang.Class);
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+    method public boolean isScrolling();
+    method public void setEntranceTransitionState(boolean);
     method public void setExpand(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
     method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
   }
 
+  public static class RowsSupportFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter {
+    ctor public RowsSupportFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsSupportFragment);
+  }
+
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter {
+    ctor public RowsSupportFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsSupportFragment);
+  }
+
   public class SearchFragment extends android.app.Fragment {
     ctor public SearchFragment();
     method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
@@ -792,6 +954,14 @@
     field public int viewType;
   }
 
+  public abstract interface BaseOnItemViewClickedListener {
+    method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+  }
+
+  public abstract interface BaseOnItemViewSelectedListener {
+    method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+  }
+
   public class BrowseFrameLayout extends android.widget.FrameLayout {
     ctor public BrowseFrameLayout(android.content.Context);
     ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet);
@@ -1465,12 +1635,14 @@
     method public void onChildViewHolderSelected(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
   }
 
-  public abstract interface OnItemViewClickedListener {
-    method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  public abstract interface OnItemViewClickedListener implements android.support.v17.leanback.widget.BaseOnItemViewClickedListener {
   }
 
-  public abstract interface OnItemViewSelectedListener {
-    method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  public abstract interface OnItemViewSelectedListener implements android.support.v17.leanback.widget.BaseOnItemViewSelectedListener {
+  }
+
+  public class PageRow extends android.support.v17.leanback.widget.Row {
+    ctor public PageRow(android.support.v17.leanback.widget.HeaderItem);
   }
 
   public class PlaybackControlsRow extends android.support.v17.leanback.widget.Row {
@@ -1531,6 +1703,10 @@
     method public void setSecondaryLabels(java.lang.String[]);
   }
 
+  public static class PlaybackControlsRow.PictureInPictureAction extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.PictureInPictureAction(android.content.Context);
+  }
+
   public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
     ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
     field public static int PAUSE;
@@ -1718,16 +1894,17 @@
   public static class RowPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
     ctor public RowPresenter.ViewHolder(android.view.View);
     method public final android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder getHeaderViewHolder();
-    method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public final android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public final android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
     method public android.view.View.OnKeyListener getOnKeyListener();
     method public final android.support.v17.leanback.widget.Row getRow();
+    method public final java.lang.Object getRowObject();
     method public final float getSelectLevel();
     method public final boolean isExpanded();
     method public final boolean isSelected();
     method public final void setActivated(boolean);
-    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
     method public void setOnKeyListener(android.view.View.OnKeyListener);
     method public final void syncActivatedStatus(android.view.View);
     field protected final android.support.v17.leanback.graphics.ColorOverlayDimmer mColorDimmer;
@@ -1742,7 +1919,9 @@
     method public android.graphics.drawable.Drawable getBadgeDrawable();
     method public java.lang.CharSequence getHint();
     method public java.lang.String getTitle();
+    method public boolean isRecognizing();
     method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setPermissionListener(android.support.v17.leanback.widget.SearchBar.SearchBarPermissionListener);
     method public void setSearchBarListener(android.support.v17.leanback.widget.SearchBar.SearchBarListener);
     method public void setSearchQuery(java.lang.String);
     method public void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
@@ -1758,6 +1937,10 @@
     method public abstract void onSearchQuerySubmit(java.lang.String);
   }
 
+  public static abstract interface SearchBar.SearchBarPermissionListener {
+    method public abstract void requestAudioPermission();
+  }
+
   public class SearchEditText extends android.support.v17.leanback.widget.StreamingTextView {
     ctor public SearchEditText(android.content.Context);
     ctor public SearchEditText(android.content.Context, android.util.AttributeSet);
diff --git a/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java b/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java
new file mode 100644
index 0000000..19cf5d6
--- /dev/null
+++ b/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java
@@ -0,0 +1,23 @@
+/*
+ * 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.v17.leanback.app;
+
+class PermissionHelper23 {
+
+    public static void requestPermissions(android.app.Fragment fragment, String[] permissions,
+                                          int requestCode) {
+        fragment.requestPermissions(permissions, requestCode);
+    }
+
+}
diff --git a/v17/leanback/generatev4.py b/v17/leanback/generatev4.py
index fbc52f2..fb23876 100755
--- a/v17/leanback/generatev4.py
+++ b/v17/leanback/generatev4.py
@@ -52,3 +52,4 @@
     outfile.write(line)
 file.close()
 outfile.close()
+
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
new file mode 100644
index 0000000..1a59e47
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
Binary files differ
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
index bc8ffc3..3ef214e 100644
--- a/v17/leanback/res/layout/lb_browse_fragment.xml
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -27,12 +27,19 @@
         android:descendantFocusability="afterDescendants"
         android:id="@+id/browse_frame"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        >
+        android:layout_height="match_parent">
+
         <android.support.v17.leanback.widget.BrowseRowsFrameLayout
             android:id="@+id/browse_container_dock"
             android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+            android:layout_height="match_parent">
+
+            <android.support.v17.leanback.widget.ScaleFrameLayout
+                android:id="@+id/scale_frame"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+
+        </android.support.v17.leanback.widget.BrowseRowsFrameLayout>
 
         <!-- Padding needed for shadow -->
         <FrameLayout
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
index 0a5112d..fc5e706 100644
--- a/v17/leanback/res/layout/lb_rows_fragment.xml
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -14,17 +14,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<android.support.v17.leanback.widget.ScaleFrameLayout
+<android.support.v17.leanback.widget.VerticalGridView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/scale_frame"
+    android:id="@+id/container_list"
     android:layout_width="match_parent"
-    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"
-        android:clipToPadding="false"
-        style="?attr/rowsVerticalGridStyle" />
-
-</android.support.v17.leanback.widget.ScaleFrameLayout>
\ No newline at end of file
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    style="?attr/rowsVerticalGridStyle" />
\ 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 a450df0..1167402 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiveer hoë gehalte"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktiveer onderskrifte"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiveer onderskrifte"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Voer prent in prentmodus in"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooi"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Gaan voort"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index dc048b3..56a623d 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ስዕሉን በስዕል ሁነታ ውስጥ ያክሉ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ጨርስ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ቀጥል"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index c216199..84bba5f 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"إدخال صورة في وضع الصورة"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"إنهاء"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"متابعة"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az-rAZ/strings.xml
index 9d9de73..15ddc65 100644
--- a/v17/leanback/res/values-az-rAZ/strings.xml
+++ b/v17/leanback/res/values-az-rAZ/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksək keyfiyyəti deaktiv edin"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Qapalı çəkilişi aktiv edin"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Qapalı çəkilişi deaktiv edin"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Şəkil içində Şəkil Rejiminə daxil olun"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Bitir"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davam edin"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-b+sr+Latn/strings.xml b/v17/leanback/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..79afeb3
--- /dev/null
+++ b/v17/leanback/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Radnja pretrage"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Govorite da biste pretraživali"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pretražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite da biste pretražili <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">"Pusti"</string>
+    <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauziraj"</string>
+    <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Premotaj unapred"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Premotaj unapred %1$dX"</string>
+    <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Premotaj unazad"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Premotaj unazad %1$dX"</string>
+    <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči sledeću"</string>
+    <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prethodnu"</string>
+    <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Još radnji"</string>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Opozovi izbor palca nagore"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Izaberi palac nagore"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Opozovi izbor palca nadole"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Izaberi palac nadole"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ne ponavljaj nijednu"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ponovi sve"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ponovi jednu"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Omogući nasumičnu reprodukciju"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Onemogući nasumičnu reprodukciju"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Omogući visok kvalitet"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visok kvalitet"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u režim Slika u slici"</string>
+    <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dovrši"</string>
+    <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
+    <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+    <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+    <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+    <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Dalje"</string>
+</resources>
diff --git a/v17/leanback/res/values-be-rBY/strings.xml b/v17/leanback/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..bfc056a
--- /dev/null
+++ b/v17/leanback/res/values-be-rBY/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Аперацыя пошуку"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Шукаць"</string>
+    <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_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>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Калі ласка, увядзіце выяву ў рэжыме Выяў"</string>
+    <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Завяршыць"</string>
+    <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Далей"</string>
+    <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+    <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+    <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ПАЧАЦЬ"</string>
+    <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Далей"</string>
+</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index 4be5a2c..89114dc 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Вход в режима „Картина в картина“"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Край"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Напред"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index 82b7d57..d73964e 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ছবি মোডে ছবি লগান"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"শেষ করুন"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"চালিয়ে যান"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-bs-rBA/strings.xml b/v17/leanback/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..0e19743
--- /dev/null
+++ b/v17/leanback/res/values-bs-rBA/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Pretraživanje"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretraga"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Kažite nešto da pokrenete pretragu"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pretraži <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kažite nešto da pokrenete pretragu <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">"Ubrzaj"</string>
+    <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Ubrzaj %1$dX"</string>
+    <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Premotaj"</string>
+    <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Premotaj %1$dX"</string>
+    <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči sljedeće"</string>
+    <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prethodno"</string>
+    <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Više radnji"</string>
+    <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Poništi pozitivnu ocjenu"</string>
+    <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Odaberi pozitivnu ocjenu"</string>
+    <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Poništi negativnu ocjenu"</string>
+    <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Odaberi negativnu ocjenu"</string>
+    <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ne ponavljaj"</string>
+    <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ponovi sve"</string>
+    <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ponovi jedno"</string>
+    <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Uključi izmiješani redoslijed"</string>
+    <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Isključi izmiješani redoslijed"</string>
+    <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Uključi visoki kvalitet"</string>
+    <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Isključi visoki kvalitet"</string>
+    <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Uključi titlove"</string>
+    <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Isključi titlove"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u način rada Slika u slici"</string>
+    <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Završiti"</string>
+    <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastaviti"</string>
+    <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+    <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+    <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+    <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Sljedeća"</string>
+</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index f0e0661..ca2867a 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactiva l\'alta qualitat"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activa els subtítols tancats"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactiva els subtítols tancats"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entra al mode d\'imatge en imatge"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalitza"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index 1dd0bcc..9bb0026 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Vypnout vysokou kvalitu"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnout titulky"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnout titulky"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Přejít do režimu Obraz v obraze"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončit"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovat"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 454df06..63304a3 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høj kvalitet"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér undertekster"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver undertekster"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tilføj billedet i billedtilstand"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Afslut"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsæt"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index e7b95ac..17d05ee 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hohe Qualität deaktivieren"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Untertitel aktivieren"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Untertitel deaktivieren"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Bild-in-Bild-Modus aktivieren"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fertigstellen"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Weiter"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index 659bed4..ea120b1 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Εισαγωγή εικόνας στη Λειτουργία εικόνας"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Τέλος"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Συνέχεια"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rAU/strings.xml b/v17/leanback/res/values-en-rAU/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rAU/strings.xml
+++ b/v17/leanback/res/values-en-rAU/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 9b1778f..5b5fd2e 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 1574470..6cdd4b4 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar calidad alta"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar el modo Imagen en imagen"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index 6420a61..09d10f2 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar alta calidad"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar modo Imagen en imagen"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index a223081..016bf42 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Keela kõrgkvaliteetne taasesitus"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Luba subtiitrid"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Keela subtiitrid"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Sisene režiimi Pilt pildis"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Lõpeta"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jätka"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index dfec00a..f081a12 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desgaitu kalitate handiko erreprodukzioa"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Gaitu azpitituluak"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desgaitu azpitituluak"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktibatu \"Argazkia argazkian\" modua"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Amaitu"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jarraitu"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index c4cc00e..1d3e007 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"وارد حالت تصویر در تصویر شوید"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"پایان"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ادامه"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index 89cc6e4..747b10c 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -46,6 +46,7 @@
     <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>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vaihda kuva kuvassa ‑tilaan"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Valmis"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jatka"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index fc4ecbf..5da936f 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la lecture haute qualité"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer le sous-titrage"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver le sous-titrage"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode Incrustation d\'image"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index 34c915a..57a649d 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la haute qualité"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer les sous-titres"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver les sous-titres"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode PIP"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index c73bdc6..eebba2a 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactivar alta calidade"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activar subtítulos"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactivar subtítulos"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar o modo Imaxe superposta"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-gu-rIN/strings.xml b/v17/leanback/res/values-gu-rIN/strings.xml
index 5070456..df25bbe 100644
--- a/v17/leanback/res/values-gu-rIN/strings.xml
+++ b/v17/leanback/res/values-gu-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ચિત્ર મોડમાં ચિત્ર દાખલ કરો"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"સમાપ્ત કરો"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ચાલુ રાખો"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index 71e1b06..9432182 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"चित्र मोड में चित्र डालें"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त करें"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी रखें"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index e5c77da..8309d47 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -21,7 +21,7 @@
     <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
     <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 da pretražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit da pretražite uslugu <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>
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visoku kvalitetu"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Unos slike u načinu slike"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Završi"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index 89b28b5..9662419 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Jó minőségű lejátszás letiltása"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Feliratok engedélyezése"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Feliratok letiltása"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Kép a képben mód indítása"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Befejezés"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Folytatás"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 2be3c57..2ea16d1 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Մուտք «Նկար նկարի մեջ» ռեժիմ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Վերջ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Շարունակել"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index f66a247..14c2438 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Nonaktifkan Kualitas Tinggi"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktifkan Pembuatan Teks"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Nonaktifkan Pembuatan Teks"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Foto Dalam Mode Foto"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Lanjutkan"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index 03cb9f0..ec83043 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Slökkva á miklum gæðum"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Kveikja á skjátextum"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Slökkva á skjátextum"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Skoða mynd í myndsniði"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Ljúka"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Halda áfram"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 2318948..ffc0788 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disattiva alta qualità"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Attiva sottotitoli"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disattiva sottotitoli"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Attiva modalità Picture-in-picture"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fine"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index a390964..ddbd626 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -19,9 +19,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="orb_search_action" msgid="5651268540267663887">"פעולת חיפוש"</string>
     <string name="lb_search_bar_hint" msgid="8325490927970116252">"חפש"</string>
-    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
+    <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_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>
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"הזן את התמונה במצב תמונה"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"סיום"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"המשך"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 74d8d3e..9914220 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"PIP モードに移動"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完了"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"続行"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index ef03a42..4d52ce9 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -50,6 +50,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"რეჟიმზე „სურათი სურათში“ გადასვლა"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"დასრულება"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"გაგრძელება"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index 9bea95b..850382e 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Сурет ішіндегі сурет режиміне кіру"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Аяқтау"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Жалғастыру"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index 7bcfcdd..d67aabb 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"បញ្ចូលរូបភាពនៅក្នុងរបៀបរូបភាព"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"បញ្ចប់"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"បន្ត"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 7660eea..692b0a6 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ಚಿತ್ರವನ್ನು ಚಿತ್ರ ಮೋಡ್‌ನಲ್ಲಿ ಪ್ರವೇಶಿಸಿ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ಪೂರ್ಣಗೊಳಿಸು"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ಮುಂದುವರಿಸು"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index 63e265c..89d961b 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"사진 모드에서 사진 입력"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"완료"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"계속"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index 70b3f5e..0dcd67c 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Сүрөт режиминде сүрөт киргизүү"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Бүтүрүү"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Улантуу"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index 0e5c840..27ba937 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ປ້ອນຮູບພາບໃນໂໝດຮູບພາບ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ສໍາເລັດ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"​ສືບ​ຕໍ່"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index 0183026..0ce4deb 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Išjungti aukštą kokybę"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Įgalinti subtitrus"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Išjungti subtitrus"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Įjungti vaizdo vaizde režimą"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Baigti"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tęsti"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index d453eac..5abc6e6 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Atspējot augstas kvalitātes vienumu atskaņošanu"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Iespējot slēgtos parakstus"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Atspējot slēgtos parakstus"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktivizēt režīmu Attēls attēlā"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Pabeigt"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Turpināt"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index b3f76e0..3f482f6 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Влези во режимот „Слика во слика“"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Заврши"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продолжи"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index d90e4e9..5c24bd8 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"\'ചിത്രത്തിനുള്ളിൽ ചിത്രം\' മോഡിലേക്ക് പ്രവേശിക്കുക"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"പൂര്‍ത്തിയാക്കുക"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"തുടരുക"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index f0acfeb..8910e54 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Зургийн горимд зураг оруулна уу"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Дуусгах"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Үргэлжлүүлэх"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index 15222be..03160f4 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र प्रविष्ट करा"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"सुरू ठेवा"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index d2a73c6..c61d914 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -46,6 +46,7 @@
     <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>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Gambar Dalam Mod Gambar"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Teruskan"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index df3ef0c..f80c236 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ဓာတ်ပုံမုဒ်တွင် ဓာတ်ပုံထည့်ပါ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ပြီးပြီ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ဆက်လုပ်ရန်"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index dc0b3c4..ebf9870 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høy kvalitet"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér teksting"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver teksting"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Slå på modusen Bilde-i-bilde"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fullfør"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsett"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 83d3e53..538325b 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -48,6 +48,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"चित्रलाई चित्र मोडमा प्रविष्ट गर्नुहोस्"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त गर्नुहोस्"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी राख्नुहोस्"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 0eceed4..2379624 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hoge kwaliteit uitschakelen"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ondertiteling inschakelen"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Ondertiteling uitschakelen"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Beeld-in-beeld-modus openen"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooien"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Doorgaan"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"-"</string>
diff --git a/v17/leanback/res/values-pa-rIN/strings.xml b/v17/leanback/res/values-pa-rIN/strings.xml
index db3b36b..80677a2 100644
--- a/v17/leanback/res/values-pa-rIN/strings.xml
+++ b/v17/leanback/res/values-pa-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"ਤਸਵੀਰ ਮੋਡ ਵਿੱਚ ਤਸਵੀਰ ਦਾਖਲ ਕਰੋ"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ਖ਼ਤਮ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index 5a5d42d..0bb297f 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -21,7 +21,7 @@
     <string name="lb_search_bar_hint" msgid="8325490927970116252">"Szukaj"</string>
     <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ć <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, co chcesz wyszukać w usłudze <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>
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Wyłącz wysoką jakość"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Włącz napisy"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Wyłącz napisy"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Włącz tryb obrazu w obrazie"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Zakończ"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Dalej"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-pt-rBR/strings.xml b/v17/leanback/res/values-pt-rBR/strings.xml
index 96e4399..4418a44 100644
--- a/v17/leanback/res/values-pt-rBR/strings.xml
+++ b/v17/leanback/res/values-pt-rBR/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index aae336f..7ddbd7e 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar legendas"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar legendas"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Imagem na imagem"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 96e4399..4418a44 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index ead151b..427ae5e 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -46,6 +46,7 @@
     <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>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activați modul Picture-in-Picture"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizați"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuați"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index ba7dd82..88a0c27 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Включить режим \"Картинка в картинке\"."</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Готово"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Далее"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index 267ed12..1af8590 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"පින්තූරය-තුළ-පින්තූරය ප්‍රකාරයට ඇතුළු වන්න"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"අවසානය"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"දිගටම කර ගෙන යන්න"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index 6107eeb..8556c91 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zakázať médiá vo vysokej kvalite"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnúť skryté titulky"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnúť skryté titulky"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vložiť obrázok v režime obrázka"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončiť"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovať"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index bfa620d..64664d8 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -21,7 +21,7 @@
     <string name="lb_search_bar_hint" msgid="8325490927970116252">"Iskanje"</string>
     <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite iskalno poizvedbo"</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">"Izgovorite poizvedbo za iskanje v <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite poizvedbo za iskanje v storitvi <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>
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogoči visoko kakovost"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogoči podnapise"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogoči podnapise"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vklop načina za sliko v sliki"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončaj"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Naprej"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sq-rAL/strings.xml b/v17/leanback/res/values-sq-rAL/strings.xml
index 8f453d5..73d6e26 100644
--- a/v17/leanback/res/values-sq-rAL/strings.xml
+++ b/v17/leanback/res/values-sq-rAL/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Çaktivizo \"Cilësinë e lartë\""</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivizo titrat"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Çaktivizo titrat me sekuencë kohore"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Fut një fotografi në modalitetin e fotografisë"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Përfundo"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Vazhdo"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index a63a28f..96c34b2 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Уђи у режим Слика у слици"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Доврши"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Настави"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index 34452c2..d6d9245 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inaktivera hög kvalitet"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivera textning"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inaktivera textning"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ange läget Bild-i-bild"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Slutför"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsätt"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index ddf7cc0..c770586 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zima Ubora wa Juu"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Washa manukuu"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Zima manukuu"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Weka Picha Katika Hali ya Picha"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Kamilisha"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Endelea"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index a5e14ea..8f64ca4 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"பிக்ச்சர் இன் பிக்ச்சர் பயன்முறைக்குச் செல்"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"முடி"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"தொடர்க"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index bf9f96b..13856be 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"చిత్రంలో చిత్రం మోడ్‌లోకి ప్రవేశించండి"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ముగించు"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"కొనసాగించు"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index c239b7a..dcd027d 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"เข้าสู่โหมดการแสดงผลหลายแหล่งพร้อมกัน"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"เสร็จสิ้น"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ต่อไป"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index d56150e..4715c92 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -46,6 +46,7 @@
     <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 Subtitle"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Subtitle"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Pumasok sa Picture In Picture Mode"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tapusin"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Magpatuloy"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index eaf7950..f61ca42 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksek Kalitede Oynatmayı Devre Dışı Bırak"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Altyazıları Etkinleştir"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Altyazıları Devre Dışı Bırak"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Resim İçinde Resim Moduna Geç"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Son"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Devam"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index f539de5..52b4655 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"Перейти в режим \"Картинка в картинці\""</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Закінчити"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продовжити"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index 706157e..95abc14 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"\'تصویر میں تصویر موڈ\' میں داخل ہوں"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"مکمل کریں"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"جاری رکھیں"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index a608570..99668e1 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yuqori sifatni o‘chirib qo‘yish"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Taglavhalarni yoqish"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Taglavhalarni o‘chirib qo‘yish"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tasvir ichida tasvir rejimiga kirish"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tugatish"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davom etish"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index 5576b47..8c543e7 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Tắt chế độ chất lượng cao"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Bật phụ đề"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Tắt phụ đề"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vào ảnh ở chế độ ảnh"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Hoàn tất"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tiếp tục"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index 86fa1d5..8d5da49 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"进入画中画模式"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"继续"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 7709e9a..30cf3df 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"進入「畫中畫模式」"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index de53c63..9334ea0 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -46,6 +46,7 @@
     <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_picture_in_picture" msgid="3040035547765350690">"進入子母畫面模式"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index aefea61..6590d69 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -46,6 +46,7 @@
     <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Khubaza ikhwalithi ephezulu"</string>
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Nika amandla imibhalo engezansi"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Khubaza imihbalo engezansi"</string>
+    <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ngena isithombe kumodi yesithombe"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Qeda"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Qhubeka"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 9d1cae2..7d8677e 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -166,6 +166,7 @@
         <attr name="shuffle" format="reference"/>
         <attr name="high_quality" format="reference"/>
         <attr name="closed_captioning" format="reference"/>
+        <attr name="picture_in_picture" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="lbSlide">
@@ -489,43 +490,34 @@
         <attr name="datePickerFormat" format="string"/>
     </declare-styleable>
 
-    <!-- @hide -->
     <declare-styleable name="LeanbackOnboardingTheme">
-        <!-- @hide
-             Theme attribute for the overall theme used in the onboarding. The Leanback themes set
+        <!-- Theme attribute for the overall theme used in the onboarding. The Leanback themes set
              the default for this, but a custom theme that does not derive from a leanback theme can
              set this to <code>@style/Theme.Leanback.Onboarding</code> in order to specify the
              default OnboardingFragment styles. -->
         <attr name="onboardingTheme" format="reference" />
 
-        <!-- @hide
-             Theme attribute for the style of the header in onboarding screen. Default is
+        <!-- Theme attribute for the style of the header in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingHeaderStyle}.-->
         <attr name="onboardingHeaderStyle" format="reference" />
-        <!-- @hide
-             Theme attribute for the style of the title text in onboarding screen. Default is
+        <!-- Theme attribute for the style of the title text in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingTitleStyle}.-->
         <attr name="onboardingTitleStyle" format="reference" />
-        <!-- @hide
-             Theme attribute for the style of the description text in onboarding screen. Default is
+        <!-- Theme attribute for the style of the description text in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingDescriptionStyle}.-->
         <attr name="onboardingDescriptionStyle" format="reference" />
 
-        <!-- @hide
-             Theme attribute for the style of the navigator container in onboarding screen. Default is
+        <!-- Theme attribute for the style of the navigator container in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingNavigatorContainerStyle}.-->
         <attr name="onboardingNavigatorContainerStyle" format="reference" />
-        <!-- @hide
-             Theme attribute for the style of the page indicator in onboarding screen. Default is
+        <!-- Theme attribute for the style of the page indicator in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingPageIndicatorStyle}.-->
         <attr name="onboardingPageIndicatorStyle" format="reference" />
-        <!-- @hide
-             Theme attribute for the style of the start button in onboarding screen. Default is
+        <!-- Theme attribute for the style of the start button in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingStartButtonStyle}.-->
         <attr name="onboardingStartButtonStyle" format="reference" />
 
-        <!-- @hide
-             Theme attribute for the style of the logo in onboarding screen. Default is
+        <!-- Theme attribute for the style of the logo in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingLogoStyle}.-->
         <attr name="onboardingLogoStyle" format="reference" />
     </declare-styleable>
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
index 98ac1fd..ab7f568 100644
--- a/v17/leanback/res/values/ids.xml
+++ b/v17/leanback/res/values/ids.xml
@@ -34,5 +34,6 @@
      <item type="id" name="lb_control_shuffle" />
      <item type="id" name="lb_control_high_quality" />
      <item type="id" name="lb_control_closed_captioning" />
+     <item type="id" name="lb_control_picture_in_picture" />
 
 </resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 212b11d..62c09f8 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -74,6 +74,8 @@
     <string name="lb_playback_controls_closed_captioning_enable">Enable Closed Captioning</string>
     <!-- Talkback label for the control button to disable closed captioning -->
     <string name="lb_playback_controls_closed_captioning_disable">Disable Closed Captioning</string>
+    <!-- Talkback label for the control button to enter picture in picture mode -->
+    <string name="lb_playback_controls_picture_in_picture">Enter Picture In Picture Mode</string>
 
     <!-- Title of standard Finish action for GuidedStepFragment -->
     <string name="lb_guidedaction_finish_title">Finish</string>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index c104fd6..6af24bc 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -371,6 +371,7 @@
         <item name="shuffle">@drawable/lb_ic_shuffle</item>
         <item name="high_quality">@drawable/lb_ic_hq</item>
         <item name="closed_captioning">@drawable/lb_ic_cc</item>
+        <item name="picture_in_picture">@drawable/lb_ic_pip</item>
     </style>
 
     <!-- Style for the main container view in a GuidanceStylist's default layout. -->
@@ -554,8 +555,7 @@
     <!-- deprecated style, do not use -->
     <style name="Widget.Leanback.GuidedActionsSelectorStyle"></style>
 
-    <!-- @hide
-         Style for the header in OnboardingFragment. -->
+    <!-- Style for the header in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingHeaderStyle">
         <item name="android:layout_width">@dimen/lb_onboarding_content_width</item>
         <item name="android:layout_height">@dimen/lb_onboarding_header_height</item>
@@ -567,8 +567,7 @@
         <item name="android:orientation">vertical</item>
     </style>
 
-    <!-- @hide
-         Style for the header title in OnboardingFragment. -->
+    <!-- Style for the header title in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingTitleStyle">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">0dp</item>
@@ -581,8 +580,7 @@
         <item name="android:lineSpacingExtra">14sp</item>
     </style>
 
-    <!-- @hide
-         Style for the header description in OnboardingFragment. -->
+    <!-- Style for the header description in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingDescriptionStyle">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">0dp</item>
@@ -595,8 +593,7 @@
         <item name="android:lineSpacingExtra">10sp</item>
     </style>
 
-    <!-- @hide
-         Style for the container of page indicator and start button in OnboardingFragment. -->
+    <!-- Style for the container of page indicator and start button in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingNavigatorContainerStyle">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
@@ -605,8 +602,7 @@
         <item name="android:layout_alignParentBottom">true</item>
     </style>
 
-    <!-- @hide
-         Style for the page indicator in OnboardingFragment. -->
+    <!-- Style for the page indicator in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingPageIndicatorStyle">
         <item name="android:layout_width">@dimen/lb_onboarding_content_width</item>
         <item name="android:layout_height">@dimen/lb_onboarding_navigation_height</item>
@@ -615,8 +611,7 @@
         <item name="android:contentDescription">@string/lb_onboarding_accessibility_next</item>
     </style>
 
-    <!-- @hide
-         Style for the start button in OnboardingFragment. -->
+    <!-- Style for the start button in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingStartButtonStyle">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">36dp</item>
@@ -635,8 +630,7 @@
         <item name="android:textSize">16sp</item>
     </style>
 
-    <!-- @hide
-         Style for the logo splash image in OnboardingFragment. -->
+    <!-- Style for the logo splash image in OnboardingFragment. -->
     <style name="Widget.Leanback.OnboardingLogoStyle">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 596b602..e92bb49 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -177,7 +177,6 @@
       <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
-    <!-- @hide -->
     <style name="Theme.Leanback.Onboarding" parent="Theme.LeanbackBase">
         <item name="onboardingHeaderStyle">@style/Widget.Leanback.OnboardingHeaderStyle</item>
         <item name="onboardingTitleStyle">@style/Widget.Leanback.OnboardingTitleStyle</item>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/Adaptable.java b/v17/leanback/src/android/support/v17/leanback/app/Adaptable.java
new file mode 100644
index 0000000..de37137
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/Adaptable.java
@@ -0,0 +1,17 @@
+package android.support.v17.leanback.app;
+
+/**
+ * Generic interface to implement the adapter pattern.
+ * {@link RowsFragment} implements this interface to return adapter class used in
+ * {@link BrowseFragment}.
+ */
+public interface Adaptable {
+    /**
+     * Return the adapter for a given adaptable class. Return null if the adapter class is
+     * not supported.
+     *
+     * @param clazz The class type for the adapter.
+     * @return The adapter implementation.
+     */
+    Object getAdapter(Class clazz);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
index c878a86..cad5507 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -20,27 +20,78 @@
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.util.StateMachine.State;
+
+import static android.support.v17.leanback.util.StateMachine.*;
+
 /**
  * @hide
  */
 class BaseFragment extends BrandedFragment {
 
-    private boolean mEntranceTransitionEnabled = false;
-    private boolean mStartEntranceTransitionPending = false;
-    private boolean mEntranceTransitionPreparePending = false;
+    /**
+     * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true
+     * Action: none
+     */
+    private static final State STATE_ALLOWED = new State() {
+        @Override
+        public boolean canRun() {
+            return TransitionHelper.systemSupportsEntranceTransitions();
+        }
+    };
+
+    /**
+     * Condition: {@link #isReadyForPrepareEntranceTransition()} is true
+     * Action: {@link #onEntranceTransitionPrepare()} }
+     */
+    private final State STATE_PREPARE = new State() {
+        @Override
+        public boolean canRun() {
+            return isReadyForPrepareEntranceTransition();
+        }
+
+        @Override
+        public void run() {
+            onEntranceTransitionPrepare();
+        }
+    };
+
+    /**
+     * Condition: {@link #isReadyForStartEntranceTransition()} is true
+     * Action: {@link #onExecuteEntranceTransition()} }
+     */
+    private final State STATE_START = new State() {
+        @Override
+        public boolean canRun() {
+            return isReadyForStartEntranceTransition();
+        }
+
+        @Override
+        public void run() {
+            onExecuteEntranceTransition();
+        }
+    };
+
+    final StateMachine mEnterTransitionStates;
+
     private Object mEntranceTransition;
 
+    BaseFragment() {
+        mEnterTransitionStates = new StateMachine();
+        mEnterTransitionStates.addState(STATE_ALLOWED);
+        mEnterTransitionStates.addState(STATE_PREPARE);
+        mEnterTransitionStates.addState(STATE_START);
+    }
+
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        if (mEntranceTransitionPreparePending) {
-            mEntranceTransitionPreparePending = false;
-            onEntranceTransitionPrepare();
-        }
-        if (mStartEntranceTransitionPending) {
-            mStartEntranceTransitionPending = false;
-            startEntranceTransition();
-        }
+        performPendingStates();
+    }
+
+    final void performPendingStates() {
+        mEnterTransitionStates.runPendingStates();
     }
 
     /**
@@ -70,14 +121,8 @@
      * override the default transition that browse and details provides.
      */
     public void prepareEntranceTransition() {
-        if (TransitionHelper.systemSupportsEntranceTransitions()) {
-            mEntranceTransitionEnabled = true;
-            if (getView() == null) {
-                mEntranceTransitionPreparePending = true;
-                return;
-            }
-            onEntranceTransitionPrepare();
-        }
+        mEnterTransitionStates.runState(STATE_ALLOWED);
+        mEnterTransitionStates.runState(STATE_PREPARE);
     }
 
     /**
@@ -86,7 +131,8 @@
      * is reset to false after entrance transition is started.
      */
     boolean isEntranceTransitionEnabled() {
-        return mEntranceTransitionEnabled;
+        // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call.
+        return STATE_ALLOWED.getStatus() == STATUS_EXECUTED;
     }
 
     /**
@@ -127,6 +173,26 @@
     }
 
     /**
+     * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     */
+    boolean isReadyForPrepareEntranceTransition() {
+        return getView() != null;
+    }
+
+    /**
+     * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     */
+    boolean isReadyForStartEntranceTransition() {
+        return getView() != null;
+    }
+
+    /**
      * When fragment finishes loading data, it should call startEntranceTransition()
      * to execute the entrance transition.
      * startEntranceTransition() will start transition only if both two conditions
@@ -138,18 +204,10 @@
      * 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;
-        }
-        if (mEntranceTransitionPreparePending) {
-            mEntranceTransitionPreparePending = false;
-            onEntranceTransitionPrepare();
-        }
+        mEnterTransitionStates.runState(STATE_START);
+    }
+
+    void onExecuteEntranceTransition() {
         // wait till views get their initial position before start transition
         final View view = getView();
         view.getViewTreeObserver().addOnPreDrawListener(
@@ -158,7 +216,6 @@
             public boolean onPreDraw() {
                 view.getViewTreeObserver().removeOnPreDrawListener(this);
                 internalCreateEntranceTransition();
-                mEntranceTransitionEnabled = false;
                 if (mEntranceTransition != null) {
                     onEntranceTransitionStart();
                     runEntranceTransition(mEntranceTransition);
@@ -179,6 +236,7 @@
             public void onTransitionEnd(Object transition) {
                 mEntranceTransition = null;
                 onEntranceTransitionEnd();
+                mEnterTransitionStates.resetStatus();
             }
         });
     }
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 934c285..8a65b0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -15,13 +15,13 @@
 
 import android.app.Fragment;
 import android.os.Bundle;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.PresenterSelector;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -31,12 +31,14 @@
  * An internal base class for a fragment containing a list of rows.
  */
 abstract class BaseRowFragment extends Fragment {
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
     private ObjectAdapter mAdapter;
     private VerticalGridView mVerticalGridView;
     private PresenterSelector mPresenterSelector;
     private ItemBridgeAdapter mBridgeAdapter;
     private int mSelectedPosition = -1;
     private boolean mPendingTransitionPrepare;
+    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
 
     abstract int getLayoutResourceId();
 
@@ -72,21 +74,74 @@
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+        }
         if (mBridgeAdapter != null) {
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-            if (mSelectedPosition != -1) {
+            setAdapterAndSelection();
+        }
+        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+    }
+
+    /**
+     * This class waits for the adapter to be updated before setting the selected
+     * row.
+     */
+    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+        boolean mIsLateSelection = false;
+
+        public void onChanged() {
+            performLateSelection();
+        }
+
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            performLateSelection();
+        }
+
+        void startLateSelection() {
+            mIsLateSelection = true;
+            mBridgeAdapter.registerAdapterDataObserver(this);
+        }
+
+        void performLateSelection() {
+            clear();
+            if (mVerticalGridView != null) {
                 mVerticalGridView.setSelectedPosition(mSelectedPosition);
             }
         }
-        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+
+        void clear() {
+            if (mIsLateSelection) {
+                mIsLateSelection = false;
+                mBridgeAdapter.unregisterAdapterDataObserver(this);
+            }
+        }
+    }
+
+    void setAdapterAndSelection() {
+        mVerticalGridView.setAdapter(mBridgeAdapter);
+        // We don't set the selected position unless we've data in the adapter.
+        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+        if (lateSelection) {
+            mLateSelectionObserver.startLateSelection();
+        } else if (mSelectedPosition >= 0) {
+            mVerticalGridView.setSelectedPosition(mSelectedPosition);
+        }
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
+        mLateSelectionObserver.clear();
         mVerticalGridView = null;
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+    }
+
     /**
      * Set the presenter selector used to create and bind views.
      */
@@ -148,6 +203,9 @@
         }
         mSelectedPosition = position;
         if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+            if (mLateSelectionObserver.mIsLateSelection) {
+                return;
+            }
             if (smooth) {
                 mVerticalGridView.setSelectedPositionSmooth(position);
             } else {
@@ -163,6 +221,7 @@
     void updateAdapter() {
         if (mBridgeAdapter != null) {
             // detach observer from ObjectAdapter
+            mLateSelectionObserver.clear();
             mBridgeAdapter.clear();
             mBridgeAdapter = null;
         }
@@ -172,10 +231,7 @@
             mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
         }
         if (mVerticalGridView != null) {
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-            if (mBridgeAdapter != null && mSelectedPosition != -1) {
-                mVerticalGridView.setSelectedPosition(mSelectedPosition);
-            }
+            setAdapterAndSelection();
         }
     }
 
@@ -187,7 +243,7 @@
         }
     }
 
-    boolean onTransitionPrepare() {
+    public boolean onTransitionPrepare() {
         if (mVerticalGridView != null) {
             mVerticalGridView.setAnimateChildLayout(false);
             mVerticalGridView.setScrollEnabled(false);
@@ -197,7 +253,7 @@
         return false;
     }
 
-    void onTransitionStart() {
+    public void onTransitionStart() {
         if (mVerticalGridView != null) {
             mVerticalGridView.setPruneChild(false);
             mVerticalGridView.setLayoutFrozen(true);
@@ -205,7 +261,7 @@
         }
     }
 
-    void onTransitionEnd() {
+    public void onTransitionEnd() {
         // be careful that fragment might be destroyed before header transition ends.
         if (mVerticalGridView != null) {
             mVerticalGridView.setLayoutFrozen(false);
@@ -216,19 +272,15 @@
         }
     }
 
-    void setItemAlignment() {
+    public void setAlignment(int windowAlignOffsetTop) {
         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.setWindowAlignmentOffset(windowAlignOffsetTop);
             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/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
index 1729d2e..a202fb2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -17,13 +17,13 @@
 
 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.ObjectAdapter;
 import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,12 +33,14 @@
  * An internal base class for a fragment containing a list of rows.
  */
 abstract class BaseRowSupportFragment extends Fragment {
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
     private ObjectAdapter mAdapter;
     private VerticalGridView mVerticalGridView;
     private PresenterSelector mPresenterSelector;
     private ItemBridgeAdapter mBridgeAdapter;
     private int mSelectedPosition = -1;
     private boolean mPendingTransitionPrepare;
+    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
 
     abstract int getLayoutResourceId();
 
@@ -74,21 +76,74 @@
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+        }
         if (mBridgeAdapter != null) {
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-            if (mSelectedPosition != -1) {
+            setAdapterAndSelection();
+        }
+        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+    }
+
+    /**
+     * This class waits for the adapter to be updated before setting the selected
+     * row.
+     */
+    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+        boolean mIsLateSelection = false;
+
+        public void onChanged() {
+            performLateSelection();
+        }
+
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            performLateSelection();
+        }
+
+        void startLateSelection() {
+            mIsLateSelection = true;
+            mBridgeAdapter.registerAdapterDataObserver(this);
+        }
+
+        void performLateSelection() {
+            clear();
+            if (mVerticalGridView != null) {
                 mVerticalGridView.setSelectedPosition(mSelectedPosition);
             }
         }
-        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+
+        void clear() {
+            if (mIsLateSelection) {
+                mIsLateSelection = false;
+                mBridgeAdapter.unregisterAdapterDataObserver(this);
+            }
+        }
+    }
+
+    void setAdapterAndSelection() {
+        mVerticalGridView.setAdapter(mBridgeAdapter);
+        // We don't set the selected position unless we've data in the adapter.
+        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+        if (lateSelection) {
+            mLateSelectionObserver.startLateSelection();
+        } else if (mSelectedPosition >= 0) {
+            mVerticalGridView.setSelectedPosition(mSelectedPosition);
+        }
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
+        mLateSelectionObserver.clear();
         mVerticalGridView = null;
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+    }
+
     /**
      * Set the presenter selector used to create and bind views.
      */
@@ -150,6 +205,9 @@
         }
         mSelectedPosition = position;
         if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+            if (mLateSelectionObserver.mIsLateSelection) {
+                return;
+            }
             if (smooth) {
                 mVerticalGridView.setSelectedPositionSmooth(position);
             } else {
@@ -165,6 +223,7 @@
     void updateAdapter() {
         if (mBridgeAdapter != null) {
             // detach observer from ObjectAdapter
+            mLateSelectionObserver.clear();
             mBridgeAdapter.clear();
             mBridgeAdapter = null;
         }
@@ -174,10 +233,7 @@
             mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
         }
         if (mVerticalGridView != null) {
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-            if (mBridgeAdapter != null && mSelectedPosition != -1) {
-                mVerticalGridView.setSelectedPosition(mSelectedPosition);
-            }
+            setAdapterAndSelection();
         }
     }
 
@@ -189,7 +245,7 @@
         }
     }
 
-    boolean onTransitionPrepare() {
+    public boolean onTransitionPrepare() {
         if (mVerticalGridView != null) {
             mVerticalGridView.setAnimateChildLayout(false);
             mVerticalGridView.setScrollEnabled(false);
@@ -199,7 +255,7 @@
         return false;
     }
 
-    void onTransitionStart() {
+    public void onTransitionStart() {
         if (mVerticalGridView != null) {
             mVerticalGridView.setPruneChild(false);
             mVerticalGridView.setLayoutFrozen(true);
@@ -207,7 +263,7 @@
         }
     }
 
-    void onTransitionEnd() {
+    public void onTransitionEnd() {
         // be careful that fragment might be destroyed before header transition ends.
         if (mVerticalGridView != null) {
             mVerticalGridView.setLayoutFrozen(false);
@@ -218,19 +274,15 @@
         }
     }
 
-    void setItemAlignment() {
+    public void setAlignment(int windowAlignOffsetTop) {
         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.setWindowAlignmentOffset(windowAlignOffsetTop);
             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
index b9c7d58..a544d06 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -22,27 +22,78 @@
 import android.view.View;
 import android.view.ViewTreeObserver;
 
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.util.StateMachine.State;
+
+import static android.support.v17.leanback.util.StateMachine.*;
+
 /**
  * @hide
  */
 class BaseSupportFragment extends BrandedSupportFragment {
 
-    private boolean mEntranceTransitionEnabled = false;
-    private boolean mStartEntranceTransitionPending = false;
-    private boolean mEntranceTransitionPreparePending = false;
+    /**
+     * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true
+     * Action: none
+     */
+    private static final State STATE_ALLOWED = new State() {
+        @Override
+        public boolean canRun() {
+            return TransitionHelper.systemSupportsEntranceTransitions();
+        }
+    };
+
+    /**
+     * Condition: {@link #isReadyForPrepareEntranceTransition()} is true
+     * Action: {@link #onEntranceTransitionPrepare()} }
+     */
+    private final State STATE_PREPARE = new State() {
+        @Override
+        public boolean canRun() {
+            return isReadyForPrepareEntranceTransition();
+        }
+
+        @Override
+        public void run() {
+            onEntranceTransitionPrepare();
+        }
+    };
+
+    /**
+     * Condition: {@link #isReadyForStartEntranceTransition()} is true
+     * Action: {@link #onExecuteEntranceTransition()} }
+     */
+    private final State STATE_START = new State() {
+        @Override
+        public boolean canRun() {
+            return isReadyForStartEntranceTransition();
+        }
+
+        @Override
+        public void run() {
+            onExecuteEntranceTransition();
+        }
+    };
+
+    final StateMachine mEnterTransitionStates;
+
     private Object mEntranceTransition;
 
+    BaseSupportFragment() {
+        mEnterTransitionStates = new StateMachine();
+        mEnterTransitionStates.addState(STATE_ALLOWED);
+        mEnterTransitionStates.addState(STATE_PREPARE);
+        mEnterTransitionStates.addState(STATE_START);
+    }
+
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        if (mEntranceTransitionPreparePending) {
-            mEntranceTransitionPreparePending = false;
-            onEntranceTransitionPrepare();
-        }
-        if (mStartEntranceTransitionPending) {
-            mStartEntranceTransitionPending = false;
-            startEntranceTransition();
-        }
+        performPendingStates();
+    }
+
+    final void performPendingStates() {
+        mEnterTransitionStates.runPendingStates();
     }
 
     /**
@@ -72,14 +123,8 @@
      * override the default transition that browse and details provides.
      */
     public void prepareEntranceTransition() {
-        if (TransitionHelper.systemSupportsEntranceTransitions()) {
-            mEntranceTransitionEnabled = true;
-            if (getView() == null) {
-                mEntranceTransitionPreparePending = true;
-                return;
-            }
-            onEntranceTransitionPrepare();
-        }
+        mEnterTransitionStates.runState(STATE_ALLOWED);
+        mEnterTransitionStates.runState(STATE_PREPARE);
     }
 
     /**
@@ -88,7 +133,8 @@
      * is reset to false after entrance transition is started.
      */
     boolean isEntranceTransitionEnabled() {
-        return mEntranceTransitionEnabled;
+        // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call.
+        return STATE_ALLOWED.getStatus() == STATUS_EXECUTED;
     }
 
     /**
@@ -129,6 +175,26 @@
     }
 
     /**
+     * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     */
+    boolean isReadyForPrepareEntranceTransition() {
+        return getView() != null;
+    }
+
+    /**
+     * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+     * Subclass may override and add additional conditions.
+     */
+    boolean isReadyForStartEntranceTransition() {
+        return getView() != null;
+    }
+
+    /**
      * When fragment finishes loading data, it should call startEntranceTransition()
      * to execute the entrance transition.
      * startEntranceTransition() will start transition only if both two conditions
@@ -140,18 +206,10 @@
      * 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;
-        }
-        if (mEntranceTransitionPreparePending) {
-            mEntranceTransitionPreparePending = false;
-            onEntranceTransitionPrepare();
-        }
+        mEnterTransitionStates.runState(STATE_START);
+    }
+
+    void onExecuteEntranceTransition() {
         // wait till views get their initial position before start transition
         final View view = getView();
         view.getViewTreeObserver().addOnPreDrawListener(
@@ -160,7 +218,6 @@
             public boolean onPreDraw() {
                 view.getViewTreeObserver().removeOnPreDrawListener(this);
                 internalCreateEntranceTransition();
-                mEntranceTransitionEnabled = false;
                 if (mEntranceTransition != null) {
                     onEntranceTransitionStart();
                     runEntranceTransition(mEntranceTransition);
@@ -181,6 +238,7 @@
             public void onTransitionEnd(Object transition) {
                 mEntranceTransition = null;
                 onEntranceTransitionEnd();
+                mEnterTransitionStates.resetStatus();
             }
         });
     }
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 99878a9..06a93614 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -13,44 +13,44 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.annotation.ColorInt;
-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.RowHeaderPresenter;
-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.SearchOrbView;
-import android.support.v4.view.ViewCompat;
-import android.util.Log;
-import android.app.Activity;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentManager.BackStackEntry;
-import android.content.Context;
+import android.app.FragmentTransaction;
 import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+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.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+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.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
 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;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * A fragment for creating Leanback browse screens. It is composed of a
@@ -80,7 +80,8 @@
     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";
-
+    private static final String IS_PAGE_ROW = "isPageRow";
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
 
     final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
         int mLastEntryCount;
@@ -198,6 +199,255 @@
         }
     }
 
+    /**
+     * Interface that defines the interaction between {@link BrowseFragment} and it's main
+     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+     * it will be used to get the fragment to be shown in the content section. Clients can
+     * provide any implementation of fragment and customize it's interaction with
+     * {@link BrowseFragment} by overriding the necessary methods.
+     *
+     * <p>
+     * Clients are expected to provide
+     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+     * implementations of {@link MainFragmentAdapter} for given content types. Currently
+     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsFragment.MainFragmentAdapter}.
+     *
+     * <p>
+     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+     * and provide that through {@link MainFragmentAdapterRegistry}.
+     * {@link MainFragmentAdapter} implementation can supply any fragment and override
+     * just those interactions that makes sense.
+     */
+    public static class MainFragmentAdapter<T extends Fragment> {
+        private boolean mScalingEnabled;
+        private final T mFragment;
+
+        public MainFragmentAdapter(T fragment) {
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+
+        /**
+         * Returns whether its scrolling.
+         */
+        public boolean isScrolling() {
+            return false;
+        }
+
+        /**
+         * Set the visibility of titles/hovercard of browse rows.
+         */
+        public void setExpand(boolean expand) {
+        }
+
+        /**
+         * For rows that willing to participate entrance transition,  this function
+         * hide views if afterTransition is true,  show views if afterTransition is false.
+         */
+        public void setEntranceTransitionState(boolean state) {
+        }
+
+        /**
+         * Sets the window alignment and also the pivots for scale operation.
+         */
+        public void setAlignment(int windowAlignOffsetFromTop) {
+        }
+
+        /**
+         * Callback indicating transition prepare start.
+         */
+        public boolean onTransitionPrepare() {
+            return false;
+        }
+
+        /**
+         * Callback indicating transition start.
+         */
+        public void onTransitionStart() {
+        }
+
+        /**
+         * Callback indicating transition end.
+         */
+        public void onTransitionEnd() {
+        }
+
+        /**
+         * Returns whether row scaling is enabled.
+         */
+        public boolean isScalingEnabled() {
+            return mScalingEnabled;
+        }
+
+        /**
+         * Sets the row scaling property.
+         */
+        public void setScalingEnabled(boolean scalingEnabled) {
+            this.mScalingEnabled = scalingEnabled;
+        }
+    }
+
+    /**
+     * This is used to pass information to {@link RowsFragment}.
+     * {@link android.support.v17.leanback.app.RowsFragment.MainFragmentAdapter}
+     * would return an instance to connect the callbacks from {@link BrowseFragment} to
+     * {@link RowsFragment}.
+     */
+    public static class MainFragmentRowsAdapter<T extends Fragment> {
+        private final T mFragment;
+
+        public MainFragmentRowsAdapter(T fragment) {
+            if (fragment == null) {
+                throw new IllegalArgumentException("Fragment can't be null");
+            }
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+        /**
+         * Set the visibility titles/hover of browse rows.
+         */
+        public void setAdapter(ObjectAdapter adapter) {
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        }
+
+        /**
+         * Sets an item selection listener.
+         */
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        }
+
+        /**
+         * Selects a Row and perform an optional task on the Row.
+         */
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+        }
+
+        /**
+         * Selects a Row.
+         */
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+        }
+
+        /**
+         * Returns the selected position.
+         */
+        public int getSelectedPosition() {
+            return 0;
+        }
+    }
+
+    private boolean createMainFragment(ObjectAdapter adapter, int position) {
+        Object item = null;
+        if (adapter == null || adapter.size() == 0) {
+            return false;
+        } else {
+            if (position < 0) {
+                position = 0;
+            } else if (position >= adapter.size()) {
+                throw new IllegalArgumentException(
+                        String.format("Invalid position %d requested", position));
+            }
+            item = adapter.get(position);
+        }
+
+        mSelectedPosition = position;
+        boolean oldIsPageRow = mIsPageRow;
+        mIsPageRow = item instanceof PageRow;
+        boolean swap;
+
+        if (mMainFragment == null) {
+            swap = true;
+        } else {
+            if (oldIsPageRow) {
+                swap = true;
+            } else {
+                swap = mIsPageRow;
+            }
+        }
+
+        if (swap) {
+            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+            mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
+                    .getAdapter(MainFragmentAdapter.class);
+            if (!mIsPageRow) {
+                mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
+                        .getAdapter(MainFragmentRowsAdapter.class);
+                mIsPageRow = mMainFragmentRowsAdapter == null;
+            } else {
+                mMainFragmentRowsAdapter = null;
+            }
+        }
+        return swap;
+    }
+
+    /**
+     * Factory class responsible for creating fragment given the current item. {@link ListRow}
+     * should returns {@link RowsFragment} or it's subclass whereas {@link PageRow}
+     * can return any fragment class.
+     */
+    public abstract static class FragmentFactory<T extends Fragment> {
+        public abstract T createFragment(Object row);
+    }
+
+    /**
+     * FragmentFactory implementation for {@link ListRow}.
+     */
+    public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
+        @Override
+        public RowsFragment createFragment(Object row) {
+            return new RowsFragment();
+        }
+    }
+
+    /**
+     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+     * handling {@link ListRow}. Developers can override that and also if they want to
+     * use custom fragment, they can register a custom {@link FragmentFactory}
+     * against {@link PageRow}.
+     */
+    public final static class MainFragmentAdapterRegistry {
+        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap();
+        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+        public MainFragmentAdapterRegistry() {
+            registerFragment(ListRow.class, sDefaultFragmentFactory);
+        }
+
+        public void registerFragment(Class rowClass, FragmentFactory factory) {
+            mItemToFragmentFactoryMapping.put(rowClass, factory);
+        }
+
+        public Fragment createFragment(Object item) {
+            if (item == null) {
+                throw new IllegalArgumentException("Item can't be null");
+            }
+
+            FragmentFactory fragmentFactory = mItemToFragmentFactoryMapping.get(item.getClass());
+            if (fragmentFactory == null && !(item instanceof PageRow)) {
+                fragmentFactory = sDefaultFragmentFactory;
+            }
+
+            return fragmentFactory.createFragment(item);
+        }
+    }
+
     private static final String TAG = "BrowseFragment";
 
     private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
@@ -213,8 +463,12 @@
     /** The headers fragment is disabled and will never be shown. */
     public static final int HEADERS_DISABLED = 3;
 
-    private RowsFragment mRowsFragment;
+    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry
+            = new MainFragmentAdapterRegistry();
+    private MainFragmentAdapter mMainFragmentAdapter;
+    private Fragment mMainFragment;
     private HeadersFragment mHeadersFragment;
+    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
 
     private ObjectAdapter mAdapter;
 
@@ -223,16 +477,19 @@
     private boolean mBrandColorSet;
 
     private BrowseFrameLayout mBrowseFrame;
+    private ScaleFrameLayout mScaleFrameLayout;
     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 boolean mMainFragmentScaleEnabled = true;
     private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
     private OnItemViewClickedListener mOnItemViewClickedListener;
     private int mSelectedPosition = -1;
+    private float mScaleFactor;
+    private boolean mIsPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -307,12 +564,23 @@
      */
     public void setAdapter(ObjectAdapter adapter) {
         mAdapter = adapter;
-        if (mRowsFragment != null) {
-            mRowsFragment.setAdapter(adapter);
+        if (getView() == null) {
+            return;
+        }
+        replaceMainFragment(mSelectedPosition);
+
+        if (adapter != null) {
+            if (mMainFragmentRowsAdapter != null) {
+                mMainFragmentRowsAdapter.setAdapter(adapter);
+            }
             mHeadersFragment.setAdapter(adapter);
         }
     }
 
+    public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
+        return mMainFragmentAdapterRegistry;
+    }
+
     /**
      * Returns the adapter containing the rows for the fragment.
      */
@@ -335,11 +603,17 @@
     }
 
     /**
-     * Get currently bound RowsFragment or null if BrowseFragment has not been created yet.
-     * @return Currently bound RowsFragment or null if BrowseFragment has not been created yet.
+     * Get RowsFragment if it's bound to BrowseFragment or null if either BrowseFragment has
+     * not been created yet or a different fragment is bound to it.
+     *
+     * @return RowsFragment if it's bound to BrowseFragment or null otherwise.
      */
     public RowsFragment getRowsFragment() {
-        return mRowsFragment;
+        if (mMainFragment instanceof RowsFragment) {
+            return (RowsFragment) mMainFragment;
+        }
+
+        return null;
     }
 
     /**
@@ -358,8 +632,8 @@
      */
     public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
-        if (mRowsFragment != null) {
-            mRowsFragment.setOnItemViewClickedListener(listener);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
         }
     }
 
@@ -415,16 +689,23 @@
     }
 
     /**
-     * Enables scaling of rows when headers are present.
-     * By default enabled to increase density.
+     * @deprecated use {@link BrowseFragment#enableMainFragmentScaling(boolean)} instead.
      *
      * @param enable true to enable row scaling
      */
     public void enableRowScaling(boolean enable) {
-        mRowScaleEnabled = enable;
-        if (mRowsFragment != null) {
-            mRowsFragment.enableRowScaling(mRowScaleEnabled);
-        }
+        enableMainFragmentScaling(enable);
+    }
+
+    /**
+     * Enables scaling of main fragment when headers are present. For the page/row fragment,
+     * scaling is enabled only when both this method and
+     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableMainFragmentScaling(boolean enable) {
+        mMainFragmentScaleEnabled = enable;
     }
 
     private void startHeadersTransitionInternal(final boolean withHeaders) {
@@ -432,7 +713,9 @@
             return;
         }
         mShowingHeaders = withHeaders;
-        mRowsFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+        mMainFragmentAdapter.onTransitionPrepare();
+        mMainFragmentAdapter.onTransitionStart();
+        onExpandTransitionStart(!withHeaders, new Runnable() {
             @Override
             public void run() {
                 mHeadersFragment.onTransitionPrepare();
@@ -441,8 +724,8 @@
                 if (mBrowseTransitionListener != null) {
                     mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
                 }
-                TransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
-                        mHeadersTransition);
+                TransitionHelper.runTransition(
+                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
                 if (mHeadersBackStackEnabled) {
                     if (!withHeaders) {
                         getFragmentManager().beginTransaction()
@@ -462,10 +745,7 @@
 
     boolean isVerticalScrolling() {
         // don't run transition
-        return mHeadersFragment.getVerticalGridView().getScrollState()
-                != HorizontalGridView.SCROLL_STATE_IDLE
-                || mRowsFragment.getVerticalGridView().getScrollState()
-                != HorizontalGridView.SCROLL_STATE_IDLE;
+        return mHeadersFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
     }
 
 
@@ -486,8 +766,7 @@
             if (getTitleView() != null && getTitleView().hasFocus() &&
                     direction == View.FOCUS_DOWN) {
                 return mCanShowHeaders && mShowingHeaders ?
-                        mHeadersFragment.getVerticalGridView() :
-                        mRowsFragment.getVerticalGridView();
+                        mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
             }
 
             boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
@@ -502,7 +781,7 @@
                 if (isVerticalScrolling()) {
                     return focused;
                 }
-                return mRowsFragment.getVerticalGridView();
+                return mMainFragment.getView();
             } else {
                 return null;
             }
@@ -524,8 +803,8 @@
                     return true;
                 }
             }
-            if (mRowsFragment != null && mRowsFragment.getView() != null &&
-                    mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+            if (mMainFragment != null && mMainFragment.getView() != null &&
+                    mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                 return true;
             }
             if (getTitleView() != null &&
@@ -533,7 +812,7 @@
                 return true;
             }
             return false;
-        };
+        }
 
         @Override
         public void onRequestChildFocus(View child, View focused) {
@@ -553,6 +832,9 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
         if (mBackStackChangedListener != null) {
             mBackStackChangedListener.save(outState);
         } else {
@@ -586,6 +868,17 @@
                 }
             }
         }
+
+        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+    }
+
+    @Override
+    public void onDestroyView() {
+        mMainFragmentRowsAdapter = null;
+        mMainFragmentAdapter = null;
+        mMainFragment = null;
+        mHeadersFragment = null;
+        super.onDestroyView();
     }
 
     @Override
@@ -599,32 +892,52 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
-            mRowsFragment = new RowsFragment();
+        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
             mHeadersFragment = new HeadersFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.browse_headers_dock, mHeadersFragment)
-                    .replace(R.id.browse_container_dock, mRowsFragment).commit();
+
+            createMainFragment(mAdapter, mSelectedPosition);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersFragment);
+
+            if (mMainFragment != null) {
+                ft.replace(R.id.scale_frame, mMainFragment);
+            } else {
+                // Empty adapter used to guard against lazy adapter loading. When this
+                // fragment is instantiated, mAdapter might not have the data or might not
+                // have been set. In either of those cases mFragmentAdapter will be null.
+                // This way we can maintain the invariant that mMainFragmentAdapter is never
+                // null and it avoids doing null checks all over the code.
+                mMainFragmentAdapter = new MainFragmentAdapter(null);
+            }
+
+            ft.commit();
         } else {
             mHeadersFragment = (HeadersFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
-            mRowsFragment = (RowsFragment) getChildFragmentManager()
-                    .findFragmentById(R.id.browse_container_dock);
+            mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+            mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
+                    .getAdapter(MainFragmentAdapter.class);
+
+            mIsPageRow = savedInstanceState != null ?
+                    savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
+
+            mSelectedPosition = savedInstanceState != null ?
+                    savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
+            if (!mIsPageRow) {
+                mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable) mMainFragment)
+                        .getAdapter(MainFragmentRowsAdapter.class);
+            } else {
+                mMainFragmentRowsAdapter = null;
+            }
         }
 
         mHeadersFragment.setHeadersGone(!mCanShowHeaders);
-
-        mRowsFragment.setAdapter(mAdapter);
         if (mHeaderPresenterSelector != null) {
             mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
         }
         mHeadersFragment.setAdapter(mAdapter);
-
-        mRowsFragment.enableRowScaling(mRowScaleEnabled);
-        mRowsFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
         mHeadersFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
         mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
-        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
 
         View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
 
@@ -634,6 +947,12 @@
         mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
         mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
 
+        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+        mScaleFrameLayout.setPivotX(0);
+        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+        setupMainFragment();
+
         if (mBrandColorSet) {
             mHeadersFragment.setBackgroundColor(mBrandColor);
         }
@@ -659,6 +978,35 @@
         return root;
     }
 
+    private void setupMainFragment() {
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(mRowViewSelectedListener);
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    @Override
+    boolean isReadyForPrepareEntranceTransition() {
+        return mMainFragment != null;
+    }
+
+    @Override
+    boolean isReadyForStartEntranceTransition() {
+        return mMainFragment != null && mMainFragment.getView() != null;
+    }
+
+    void processingPendingEntranceTransition() {
+        // mMainFragment is not null at this point, it can perform prepare entrance transition.
+        performPendingStates();
+        // mMainFragment's view is going to be created in next cycle
+        getView().post(new Runnable() {
+            public void run() {
+                performPendingStates();
+            }
+        });
+    }
+
     private void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(getActivity(),
                 mShowingHeaders ?
@@ -671,7 +1019,7 @@
             @Override
             public void onTransitionEnd(Object transition) {
                 mHeadersTransition = null;
-                mRowsFragment.onTransitionEnd();
+                mMainFragmentAdapter.onTransitionEnd();
                 mHeadersFragment.onTransitionEnd();
                 if (mShowingHeaders) {
                     VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
@@ -679,7 +1027,7 @@
                         headerGridView.requestFocus();
                     }
                 } else {
-                    VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
+                    View rowsGridView = mMainFragment.getView();
                     if (rowsGridView != null && !rowsGridView.hasFocus()) {
                         rowsGridView.requestFocus();
                     }
@@ -704,15 +1052,6 @@
         }
     }
 
-    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;
@@ -726,8 +1065,21 @@
         if (DEBUG) Log.v(TAG, "showHeaders " + show);
         mHeadersFragment.setHeadersEnabled(show);
         setHeadersOnScreen(show);
-        setRowsAlignedLeft(!show);
-        mRowsFragment.setExpand(!show);
+        expandMainFragment(!show);
+    }
+
+    private void expandMainFragment(boolean expand) {
+        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+        params.leftMargin = !expand ? mContainerListMarginStart : 0;
+        mScaleFrameLayout.setLayoutParams(params);
+        mMainFragmentAdapter.setExpand(expand);
+
+        setMainFragmentAlignment();
+        final float scaleFactor = !expand
+                && mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        mScaleFrameLayout.setChildScale(scaleFactor);
     }
 
     private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
@@ -738,7 +1090,7 @@
                     return;
                 }
                 startHeadersTransitionInternal(false);
-                mRowsFragment.getVerticalGridView().requestFocus();
+                mMainFragment.getView().requestFocus();
             }
         };
 
@@ -746,7 +1098,10 @@
         @Override
         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                 RowPresenter.ViewHolder rowViewHolder, Row row) {
-            int position = mRowsFragment.getSelectedPosition();
+            if (mMainFragment == null) {
+                return;
+            }
+            int position = ((RowsFragment) mMainFragment).getSelectedPosition();
             if (DEBUG) Log.v(TAG, "row selected position " + position);
             onRowSelected(position);
             if (mExternalOnItemViewSelectedListener != null) {
@@ -780,13 +1135,32 @@
     }
 
     private void setSelection(int position, boolean smooth) {
-        if (position != NO_POSITION) {
-            mRowsFragment.setSelectedPosition(position, smooth);
-            mHeadersFragment.setSelectedPosition(position, smooth);
+        if (position == NO_POSITION) {
+            return;
+        }
+
+        mHeadersFragment.setSelectedPosition(position, smooth);
+        replaceMainFragment(position);
+
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
         }
         mSelectedPosition = position;
     }
 
+    private void replaceMainFragment(int position) {
+        if (createMainFragment(mAdapter, position)) {
+            swapBrowseContent(mMainFragment);
+            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+            setupMainFragment();
+            processingPendingEntranceTransition();
+        }
+    }
+
+    private void swapBrowseContent(Fragment fragment) {
+        getChildFragmentManager().beginTransaction().replace(R.id.scale_frame, fragment).commit();
+    }
+
     /**
      * Sets the selected row position with smooth animation.
      */
@@ -824,37 +1198,58 @@
      */
     public void setSelectedPosition(int rowPosition, boolean smooth,
             final Presenter.ViewHolderTask rowHolderTask) {
-        if (mRowsFragment == null) {
+        if (mMainFragmentAdapterRegistry == null) {
             return;
         }
         if (rowHolderTask != null) {
             startHeadersTransition(false);
         }
-        mRowsFragment.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
     }
 
     @Override
     public void onStart() {
         super.onStart();
-        mHeadersFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
-        mHeadersFragment.setItemAlignment();
-        mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
-        mRowsFragment.setItemAlignment();
-
-        mRowsFragment.setScalePivots(0, mContainerListAlignTop);
+        mHeadersFragment.setAlignment(mContainerListAlignTop);
+        setMainFragmentAlignment();
 
         if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
             mHeadersFragment.getView().requestFocus();
         } else if ((!mCanShowHeaders || !mShowingHeaders)
-                && mRowsFragment.getView() != null) {
-            mRowsFragment.getView().requestFocus();
+                && mMainFragment.getView() != null) {
+            mMainFragment.getView().requestFocus();
         }
+
         if (mCanShowHeaders) {
             showHeaders(mShowingHeaders);
         }
+
         if (isEntranceTransitionEnabled()) {
             setEntranceTransitionStartState();
         }
+        processingPendingEntranceTransition();
+    }
+
+    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+        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, mMainFragmentAdapter, getView()).execute();
+    }
+
+    private void setMainFragmentAlignment() {
+        int alignOffset = mContainerListAlignTop;
+        if (mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled()
+                && mShowingHeaders) {
+            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+        }
+        mMainFragmentAdapter.setAlignment(alignOffset);
     }
 
     /**
@@ -950,18 +1345,21 @@
     @Override
     protected void onEntranceTransitionPrepare() {
         mHeadersFragment.onTransitionPrepare();
-        mRowsFragment.onTransitionPrepare();
+        // setEntranceTransitionStartState() might be called when mMainFragment is null,
+        // make sure it is called.
+        mMainFragmentAdapter.setEntranceTransitionState(false);
+        mMainFragmentAdapter.onTransitionPrepare();
     }
 
     @Override
     protected void onEntranceTransitionStart() {
         mHeadersFragment.onTransitionStart();
-        mRowsFragment.onTransitionStart();
+        mMainFragmentAdapter.onTransitionStart();
     }
 
     @Override
     protected void onEntranceTransitionEnd() {
-        mRowsFragment.onTransitionEnd();
+        mMainFragmentAdapter.onTransitionEnd();
         mHeadersFragment.onTransitionEnd();
     }
 
@@ -975,14 +1373,49 @@
     void setEntranceTransitionStartState() {
         setHeadersOnScreen(false);
         setSearchOrbViewOnScreen(false);
-        mRowsFragment.setEntranceTransitionState(false);
+        mMainFragmentAdapter.setEntranceTransitionState(false);
     }
 
     void setEntranceTransitionEndState() {
         setHeadersOnScreen(mShowingHeaders);
         setSearchOrbViewOnScreen(true);
-        mRowsFragment.setEntranceTransitionState(true);
+        mMainFragmentAdapter.setEntranceTransitionState(true);
     }
 
-}
+    private static class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
 
+        private final View mView;
+        private final Runnable mCallback;
+        private int mState;
+        private MainFragmentAdapter mainFragmentAdapter;
+
+        final static int STATE_INIT = 0;
+        final static int STATE_FIRST_DRAW = 1;
+        final static int STATE_SECOND_DRAW = 2;
+
+        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+            mView = view;
+            mCallback = callback;
+            mainFragmentAdapter = adapter;
+        }
+
+        void execute() {
+            mView.getViewTreeObserver().addOnPreDrawListener(this);
+            mainFragmentAdapter.setExpand(false);
+            mState = STATE_INIT;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (mState == STATE_INIT) {
+                mainFragmentAdapter.setExpand(true);
+                mState = STATE_FIRST_DRAW;
+            } else if (mState == STATE_FIRST_DRAW) {
+                mCallback.run();
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mState = STATE_SECOND_DRAW;
+            }
+            return false;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index 01e7bc4..cc5e921 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -15,44 +15,44 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.annotation.ColorInt;
-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.RowHeaderPresenter;
-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.SearchOrbView;
-import android.support.v4.view.ViewCompat;
-import android.util.Log;
-import android.support.v4.app.FragmentActivity;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
 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.support.v4.app.FragmentTransaction;
 import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+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.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+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.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
 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;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * A fragment for creating Leanback browse screens. It is composed of a
@@ -82,7 +82,8 @@
     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";
-
+    private static final String IS_PAGE_ROW = "isPageRow";
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
 
     final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
         int mLastEntryCount;
@@ -200,6 +201,255 @@
         }
     }
 
+    /**
+     * Interface that defines the interaction between {@link BrowseSupportFragment} and it's main
+     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+     * it will be used to get the fragment to be shown in the content section. Clients can
+     * provide any implementation of fragment and customize it's interaction with
+     * {@link BrowseSupportFragment} by overriding the necessary methods.
+     *
+     * <p>
+     * Clients are expected to provide
+     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+     * implementations of {@link MainFragmentAdapter} for given content types. Currently
+     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}.
+     *
+     * <p>
+     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+     * and provide that through {@link MainFragmentAdapterRegistry}.
+     * {@link MainFragmentAdapter} implementation can supply any fragment and override
+     * just those interactions that makes sense.
+     */
+    public static class MainFragmentAdapter<T extends Fragment> {
+        private boolean mScalingEnabled;
+        private final T mFragment;
+
+        public MainFragmentAdapter(T fragment) {
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+
+        /**
+         * Returns whether its scrolling.
+         */
+        public boolean isScrolling() {
+            return false;
+        }
+
+        /**
+         * Set the visibility of titles/hovercard of browse rows.
+         */
+        public void setExpand(boolean expand) {
+        }
+
+        /**
+         * For rows that willing to participate entrance transition,  this function
+         * hide views if afterTransition is true,  show views if afterTransition is false.
+         */
+        public void setEntranceTransitionState(boolean state) {
+        }
+
+        /**
+         * Sets the window alignment and also the pivots for scale operation.
+         */
+        public void setAlignment(int windowAlignOffsetFromTop) {
+        }
+
+        /**
+         * Callback indicating transition prepare start.
+         */
+        public boolean onTransitionPrepare() {
+            return false;
+        }
+
+        /**
+         * Callback indicating transition start.
+         */
+        public void onTransitionStart() {
+        }
+
+        /**
+         * Callback indicating transition end.
+         */
+        public void onTransitionEnd() {
+        }
+
+        /**
+         * Returns whether row scaling is enabled.
+         */
+        public boolean isScalingEnabled() {
+            return mScalingEnabled;
+        }
+
+        /**
+         * Sets the row scaling property.
+         */
+        public void setScalingEnabled(boolean scalingEnabled) {
+            this.mScalingEnabled = scalingEnabled;
+        }
+    }
+
+    /**
+     * This is used to pass information to {@link RowsSupportFragment}.
+     * {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}
+     * would return an instance to connect the callbacks from {@link BrowseSupportFragment} to
+     * {@link RowsSupportFragment}.
+     */
+    public static class MainFragmentRowsAdapter<T extends Fragment> {
+        private final T mFragment;
+
+        public MainFragmentRowsAdapter(T fragment) {
+            if (fragment == null) {
+                throw new IllegalArgumentException("Fragment can't be null");
+            }
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+        /**
+         * Set the visibility titles/hover of browse rows.
+         */
+        public void setAdapter(ObjectAdapter adapter) {
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        }
+
+        /**
+         * Sets an item selection listener.
+         */
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        }
+
+        /**
+         * Selects a Row and perform an optional task on the Row.
+         */
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+        }
+
+        /**
+         * Selects a Row.
+         */
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+        }
+
+        /**
+         * Returns the selected position.
+         */
+        public int getSelectedPosition() {
+            return 0;
+        }
+    }
+
+    private boolean createMainFragment(ObjectAdapter adapter, int position) {
+        Object item = null;
+        if (adapter == null || adapter.size() == 0) {
+            return false;
+        } else {
+            if (position < 0) {
+                position = 0;
+            } else if (position >= adapter.size()) {
+                throw new IllegalArgumentException(
+                        String.format("Invalid position %d requested", position));
+            }
+            item = adapter.get(position);
+        }
+
+        mSelectedPosition = position;
+        boolean oldIsPageRow = mIsPageRow;
+        mIsPageRow = item instanceof PageRow;
+        boolean swap;
+
+        if (mMainFragment == null) {
+            swap = true;
+        } else {
+            if (oldIsPageRow) {
+                swap = true;
+            } else {
+                swap = mIsPageRow;
+            }
+        }
+
+        if (swap) {
+            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+            mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
+                    .getAdapter(MainFragmentAdapter.class);
+            if (!mIsPageRow) {
+                mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
+                        .getAdapter(MainFragmentRowsAdapter.class);
+                mIsPageRow = mMainFragmentRowsAdapter == null;
+            } else {
+                mMainFragmentRowsAdapter = null;
+            }
+        }
+        return swap;
+    }
+
+    /**
+     * Factory class responsible for creating fragment given the current item. {@link ListRow}
+     * should returns {@link RowsSupportFragment} or it's subclass whereas {@link PageRow}
+     * can return any fragment class.
+     */
+    public abstract static class FragmentFactory<T extends Fragment> {
+        public abstract T createFragment(Object row);
+    }
+
+    /**
+     * FragmentFactory implementation for {@link ListRow}.
+     */
+    public static class ListRowFragmentFactory extends FragmentFactory<RowsSupportFragment> {
+        @Override
+        public RowsSupportFragment createFragment(Object row) {
+            return new RowsSupportFragment();
+        }
+    }
+
+    /**
+     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+     * handling {@link ListRow}. Developers can override that and also if they want to
+     * use custom fragment, they can register a custom {@link FragmentFactory}
+     * against {@link PageRow}.
+     */
+    public final static class MainFragmentAdapterRegistry {
+        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap();
+        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+        public MainFragmentAdapterRegistry() {
+            registerFragment(ListRow.class, sDefaultFragmentFactory);
+        }
+
+        public void registerFragment(Class rowClass, FragmentFactory factory) {
+            mItemToFragmentFactoryMapping.put(rowClass, factory);
+        }
+
+        public Fragment createFragment(Object item) {
+            if (item == null) {
+                throw new IllegalArgumentException("Item can't be null");
+            }
+
+            FragmentFactory fragmentFactory = mItemToFragmentFactoryMapping.get(item.getClass());
+            if (fragmentFactory == null && !(item instanceof PageRow)) {
+                fragmentFactory = sDefaultFragmentFactory;
+            }
+
+            return fragmentFactory.createFragment(item);
+        }
+    }
+
     private static final String TAG = "BrowseSupportFragment";
 
     private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
@@ -215,8 +465,12 @@
     /** The headers fragment is disabled and will never be shown. */
     public static final int HEADERS_DISABLED = 3;
 
-    private RowsSupportFragment mRowsSupportFragment;
+    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry
+            = new MainFragmentAdapterRegistry();
+    private MainFragmentAdapter mMainFragmentAdapter;
+    private Fragment mMainFragment;
     private HeadersSupportFragment mHeadersSupportFragment;
+    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
 
     private ObjectAdapter mAdapter;
 
@@ -225,16 +479,19 @@
     private boolean mBrandColorSet;
 
     private BrowseFrameLayout mBrowseFrame;
+    private ScaleFrameLayout mScaleFrameLayout;
     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 boolean mMainFragmentScaleEnabled = true;
     private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
     private OnItemViewClickedListener mOnItemViewClickedListener;
     private int mSelectedPosition = -1;
+    private float mScaleFactor;
+    private boolean mIsPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -309,12 +566,23 @@
      */
     public void setAdapter(ObjectAdapter adapter) {
         mAdapter = adapter;
-        if (mRowsSupportFragment != null) {
-            mRowsSupportFragment.setAdapter(adapter);
+        if (getView() == null) {
+            return;
+        }
+        replaceMainFragment(mSelectedPosition);
+
+        if (adapter != null) {
+            if (mMainFragmentRowsAdapter != null) {
+                mMainFragmentRowsAdapter.setAdapter(adapter);
+            }
             mHeadersSupportFragment.setAdapter(adapter);
         }
     }
 
+    public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
+        return mMainFragmentAdapterRegistry;
+    }
+
     /**
      * Returns the adapter containing the rows for the fragment.
      */
@@ -337,11 +605,17 @@
     }
 
     /**
-     * Get currently bound RowsSupportFragment or null if BrowseSupportFragment has not been created yet.
-     * @return Currently bound RowsSupportFragment or null if BrowseSupportFragment has not been created yet.
+     * Get RowsSupportFragment if it's bound to BrowseSupportFragment or null if either BrowseSupportFragment has
+     * not been created yet or a different fragment is bound to it.
+     *
+     * @return RowsSupportFragment if it's bound to BrowseSupportFragment or null otherwise.
      */
     public RowsSupportFragment getRowsSupportFragment() {
-        return mRowsSupportFragment;
+        if (mMainFragment instanceof RowsSupportFragment) {
+            return (RowsSupportFragment) mMainFragment;
+        }
+
+        return null;
     }
 
     /**
@@ -360,8 +634,8 @@
      */
     public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
-        if (mRowsSupportFragment != null) {
-            mRowsSupportFragment.setOnItemViewClickedListener(listener);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
         }
     }
 
@@ -417,16 +691,23 @@
     }
 
     /**
-     * Enables scaling of rows when headers are present.
-     * By default enabled to increase density.
+     * @deprecated use {@link BrowseSupportFragment#enableMainFragmentScaling(boolean)} instead.
      *
      * @param enable true to enable row scaling
      */
     public void enableRowScaling(boolean enable) {
-        mRowScaleEnabled = enable;
-        if (mRowsSupportFragment != null) {
-            mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
-        }
+        enableMainFragmentScaling(enable);
+    }
+
+    /**
+     * Enables scaling of main fragment when headers are present. For the page/row fragment,
+     * scaling is enabled only when both this method and
+     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableMainFragmentScaling(boolean enable) {
+        mMainFragmentScaleEnabled = enable;
     }
 
     private void startHeadersTransitionInternal(final boolean withHeaders) {
@@ -434,7 +715,9 @@
             return;
         }
         mShowingHeaders = withHeaders;
-        mRowsSupportFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+        mMainFragmentAdapter.onTransitionPrepare();
+        mMainFragmentAdapter.onTransitionStart();
+        onExpandTransitionStart(!withHeaders, new Runnable() {
             @Override
             public void run() {
                 mHeadersSupportFragment.onTransitionPrepare();
@@ -443,8 +726,8 @@
                 if (mBrowseTransitionListener != null) {
                     mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
                 }
-                TransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
-                        mHeadersTransition);
+                TransitionHelper.runTransition(
+                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
                 if (mHeadersBackStackEnabled) {
                     if (!withHeaders) {
                         getFragmentManager().beginTransaction()
@@ -464,10 +747,7 @@
 
     boolean isVerticalScrolling() {
         // don't run transition
-        return mHeadersSupportFragment.getVerticalGridView().getScrollState()
-                != HorizontalGridView.SCROLL_STATE_IDLE
-                || mRowsSupportFragment.getVerticalGridView().getScrollState()
-                != HorizontalGridView.SCROLL_STATE_IDLE;
+        return mHeadersSupportFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
     }
 
 
@@ -488,8 +768,7 @@
             if (getTitleView() != null && getTitleView().hasFocus() &&
                     direction == View.FOCUS_DOWN) {
                 return mCanShowHeaders && mShowingHeaders ?
-                        mHeadersSupportFragment.getVerticalGridView() :
-                        mRowsSupportFragment.getVerticalGridView();
+                        mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
             }
 
             boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
@@ -504,7 +783,7 @@
                 if (isVerticalScrolling()) {
                     return focused;
                 }
-                return mRowsSupportFragment.getVerticalGridView();
+                return mMainFragment.getView();
             } else {
                 return null;
             }
@@ -526,8 +805,8 @@
                     return true;
                 }
             }
-            if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null &&
-                    mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+            if (mMainFragment != null && mMainFragment.getView() != null &&
+                    mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
                 return true;
             }
             if (getTitleView() != null &&
@@ -535,7 +814,7 @@
                 return true;
             }
             return false;
-        };
+        }
 
         @Override
         public void onRequestChildFocus(View child, View focused) {
@@ -555,6 +834,9 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
         if (mBackStackChangedListener != null) {
             mBackStackChangedListener.save(outState);
         } else {
@@ -588,6 +870,17 @@
                 }
             }
         }
+
+        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+    }
+
+    @Override
+    public void onDestroyView() {
+        mMainFragmentRowsAdapter = null;
+        mMainFragmentAdapter = null;
+        mMainFragment = null;
+        mHeadersSupportFragment = null;
+        super.onDestroyView();
     }
 
     @Override
@@ -601,32 +894,52 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
-            mRowsSupportFragment = new RowsSupportFragment();
+        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
             mHeadersSupportFragment = new HeadersSupportFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
-                    .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
+
+            createMainFragment(mAdapter, mSelectedPosition);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment);
+
+            if (mMainFragment != null) {
+                ft.replace(R.id.scale_frame, mMainFragment);
+            } else {
+                // Empty adapter used to guard against lazy adapter loading. When this
+                // fragment is instantiated, mAdapter might not have the data or might not
+                // have been set. In either of those cases mFragmentAdapter will be null.
+                // This way we can maintain the invariant that mMainFragmentAdapter is never
+                // null and it avoids doing null checks all over the code.
+                mMainFragmentAdapter = new MainFragmentAdapter(null);
+            }
+
+            ft.commit();
         } else {
             mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
-            mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
-                    .findFragmentById(R.id.browse_container_dock);
+            mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+            mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
+                    .getAdapter(MainFragmentAdapter.class);
+
+            mIsPageRow = savedInstanceState != null ?
+                    savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
+
+            mSelectedPosition = savedInstanceState != null ?
+                    savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
+            if (!mIsPageRow) {
+                mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable) mMainFragment)
+                        .getAdapter(MainFragmentRowsAdapter.class);
+            } else {
+                mMainFragmentRowsAdapter = null;
+            }
         }
 
         mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
-
-        mRowsSupportFragment.setAdapter(mAdapter);
         if (mHeaderPresenterSelector != null) {
             mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
         }
         mHeadersSupportFragment.setAdapter(mAdapter);
-
-        mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
-        mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
         mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
         mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
-        mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
 
         View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
 
@@ -636,6 +949,12 @@
         mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
         mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
 
+        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+        mScaleFrameLayout.setPivotX(0);
+        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+        setupMainFragment();
+
         if (mBrandColorSet) {
             mHeadersSupportFragment.setBackgroundColor(mBrandColor);
         }
@@ -661,6 +980,35 @@
         return root;
     }
 
+    private void setupMainFragment() {
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(mRowViewSelectedListener);
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    @Override
+    boolean isReadyForPrepareEntranceTransition() {
+        return mMainFragment != null;
+    }
+
+    @Override
+    boolean isReadyForStartEntranceTransition() {
+        return mMainFragment != null && mMainFragment.getView() != null;
+    }
+
+    void processingPendingEntranceTransition() {
+        // mMainFragment is not null at this point, it can perform prepare entrance transition.
+        performPendingStates();
+        // mMainFragment's view is going to be created in next cycle
+        getView().post(new Runnable() {
+            public void run() {
+                performPendingStates();
+            }
+        });
+    }
+
     private void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(getActivity(),
                 mShowingHeaders ?
@@ -673,7 +1021,7 @@
             @Override
             public void onTransitionEnd(Object transition) {
                 mHeadersTransition = null;
-                mRowsSupportFragment.onTransitionEnd();
+                mMainFragmentAdapter.onTransitionEnd();
                 mHeadersSupportFragment.onTransitionEnd();
                 if (mShowingHeaders) {
                     VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
@@ -681,7 +1029,7 @@
                         headerGridView.requestFocus();
                     }
                 } else {
-                    VerticalGridView rowsGridView = mRowsSupportFragment.getVerticalGridView();
+                    View rowsGridView = mMainFragment.getView();
                     if (rowsGridView != null && !rowsGridView.hasFocus()) {
                         rowsGridView.requestFocus();
                     }
@@ -706,15 +1054,6 @@
         }
     }
 
-    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;
@@ -728,8 +1067,21 @@
         if (DEBUG) Log.v(TAG, "showHeaders " + show);
         mHeadersSupportFragment.setHeadersEnabled(show);
         setHeadersOnScreen(show);
-        setRowsAlignedLeft(!show);
-        mRowsSupportFragment.setExpand(!show);
+        expandMainFragment(!show);
+    }
+
+    private void expandMainFragment(boolean expand) {
+        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+        params.leftMargin = !expand ? mContainerListMarginStart : 0;
+        mScaleFrameLayout.setLayoutParams(params);
+        mMainFragmentAdapter.setExpand(expand);
+
+        setMainFragmentAlignment();
+        final float scaleFactor = !expand
+                && mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        mScaleFrameLayout.setChildScale(scaleFactor);
     }
 
     private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
@@ -740,7 +1092,7 @@
                     return;
                 }
                 startHeadersTransitionInternal(false);
-                mRowsSupportFragment.getVerticalGridView().requestFocus();
+                mMainFragment.getView().requestFocus();
             }
         };
 
@@ -748,7 +1100,10 @@
         @Override
         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
                 RowPresenter.ViewHolder rowViewHolder, Row row) {
-            int position = mRowsSupportFragment.getSelectedPosition();
+            if (mMainFragment == null) {
+                return;
+            }
+            int position = ((RowsSupportFragment) mMainFragment).getSelectedPosition();
             if (DEBUG) Log.v(TAG, "row selected position " + position);
             onRowSelected(position);
             if (mExternalOnItemViewSelectedListener != null) {
@@ -782,13 +1137,32 @@
     }
 
     private void setSelection(int position, boolean smooth) {
-        if (position != NO_POSITION) {
-            mRowsSupportFragment.setSelectedPosition(position, smooth);
-            mHeadersSupportFragment.setSelectedPosition(position, smooth);
+        if (position == NO_POSITION) {
+            return;
+        }
+
+        mHeadersSupportFragment.setSelectedPosition(position, smooth);
+        replaceMainFragment(position);
+
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
         }
         mSelectedPosition = position;
     }
 
+    private void replaceMainFragment(int position) {
+        if (createMainFragment(mAdapter, position)) {
+            swapBrowseContent(mMainFragment);
+            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+            setupMainFragment();
+            processingPendingEntranceTransition();
+        }
+    }
+
+    private void swapBrowseContent(Fragment fragment) {
+        getChildFragmentManager().beginTransaction().replace(R.id.scale_frame, fragment).commit();
+    }
+
     /**
      * Sets the selected row position with smooth animation.
      */
@@ -826,37 +1200,58 @@
      */
     public void setSelectedPosition(int rowPosition, boolean smooth,
             final Presenter.ViewHolderTask rowHolderTask) {
-        if (mRowsSupportFragment == null) {
+        if (mMainFragmentAdapterRegistry == null) {
             return;
         }
         if (rowHolderTask != null) {
             startHeadersTransition(false);
         }
-        mRowsSupportFragment.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
     }
 
     @Override
     public void onStart() {
         super.onStart();
-        mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
-        mHeadersSupportFragment.setItemAlignment();
-        mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
-        mRowsSupportFragment.setItemAlignment();
-
-        mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
+        mHeadersSupportFragment.setAlignment(mContainerListAlignTop);
+        setMainFragmentAlignment();
 
         if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
             mHeadersSupportFragment.getView().requestFocus();
         } else if ((!mCanShowHeaders || !mShowingHeaders)
-                && mRowsSupportFragment.getView() != null) {
-            mRowsSupportFragment.getView().requestFocus();
+                && mMainFragment.getView() != null) {
+            mMainFragment.getView().requestFocus();
         }
+
         if (mCanShowHeaders) {
             showHeaders(mShowingHeaders);
         }
+
         if (isEntranceTransitionEnabled()) {
             setEntranceTransitionStartState();
         }
+        processingPendingEntranceTransition();
+    }
+
+    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+        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, mMainFragmentAdapter, getView()).execute();
+    }
+
+    private void setMainFragmentAlignment() {
+        int alignOffset = mContainerListAlignTop;
+        if (mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled()
+                && mShowingHeaders) {
+            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+        }
+        mMainFragmentAdapter.setAlignment(alignOffset);
     }
 
     /**
@@ -952,18 +1347,21 @@
     @Override
     protected void onEntranceTransitionPrepare() {
         mHeadersSupportFragment.onTransitionPrepare();
-        mRowsSupportFragment.onTransitionPrepare();
+        // setEntranceTransitionStartState() might be called when mMainFragment is null,
+        // make sure it is called.
+        mMainFragmentAdapter.setEntranceTransitionState(false);
+        mMainFragmentAdapter.onTransitionPrepare();
     }
 
     @Override
     protected void onEntranceTransitionStart() {
         mHeadersSupportFragment.onTransitionStart();
-        mRowsSupportFragment.onTransitionStart();
+        mMainFragmentAdapter.onTransitionStart();
     }
 
     @Override
     protected void onEntranceTransitionEnd() {
-        mRowsSupportFragment.onTransitionEnd();
+        mMainFragmentAdapter.onTransitionEnd();
         mHeadersSupportFragment.onTransitionEnd();
     }
 
@@ -977,14 +1375,49 @@
     void setEntranceTransitionStartState() {
         setHeadersOnScreen(false);
         setSearchOrbViewOnScreen(false);
-        mRowsSupportFragment.setEntranceTransitionState(false);
+        mMainFragmentAdapter.setEntranceTransitionState(false);
     }
 
     void setEntranceTransitionEndState() {
         setHeadersOnScreen(mShowingHeaders);
         setSearchOrbViewOnScreen(true);
-        mRowsSupportFragment.setEntranceTransitionState(true);
+        mMainFragmentAdapter.setEntranceTransitionState(true);
     }
 
-}
+    private static class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
 
+        private final View mView;
+        private final Runnable mCallback;
+        private int mState;
+        private MainFragmentAdapter mainFragmentAdapter;
+
+        final static int STATE_INIT = 0;
+        final static int STATE_FIRST_DRAW = 1;
+        final static int STATE_SECOND_DRAW = 2;
+
+        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+            mView = view;
+            mCallback = callback;
+            mainFragmentAdapter = adapter;
+        }
+
+        void execute() {
+            mView.getViewTreeObserver().addOnPreDrawListener(this);
+            mainFragmentAdapter.setExpand(false);
+            mState = STATE_INIT;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (mState == STATE_INIT) {
+                mainFragmentAdapter.setExpand(true);
+                mState = STATE_FIRST_DRAW;
+            } else if (mState == STATE_FIRST_DRAW) {
+                mCallback.run();
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mState = STATE_SECOND_DRAW;
+            }
+            return false;
+        }
+    }
+}
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 2a1c88c..105f8b8 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -21,11 +21,10 @@
 import android.support.v17.leanback.widget.ItemAlignmentFacet;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 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.TitleHelper;
 import android.support.v17.leanback.widget.TitleView;
@@ -41,8 +40,7 @@
  *
  * <p>
  * A DetailsFragment 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}, the Adapter's {@link PresenterSelector} must maintains subclasses
+ * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
  * of {@link RowPresenter}.
  * </p>
  *
@@ -92,18 +90,18 @@
 
     private ObjectAdapter mAdapter;
     private int mContainerListAlignTop;
-    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private BaseOnItemViewClickedListener mOnItemViewClickedListener;
 
     private Object mSceneAfterEntranceTransition;
 
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
 
-    private final OnItemViewSelectedListener mOnItemViewSelectedListener =
-            new OnItemViewSelectedListener() {
+    private final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener<Object>() {
         @Override
         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
+                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
             int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
             int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition();
             if (DEBUG) Log.v(TAG, "row selected position " + position
@@ -144,14 +142,14 @@
     /**
      * Sets an item selection listener.
      */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
         mExternalOnItemViewSelectedListener = listener;
     }
 
     /**
      * Sets an item clicked listener.
      */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
         if (mOnItemViewClickedListener != listener) {
             mOnItemViewClickedListener = listener;
             if (mRowsFragment != null) {
@@ -163,7 +161,7 @@
     /**
      * Returns the item clicked listener.
      */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
         return mOnItemViewClickedListener;
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
index 3dce62c..fe415ec 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -23,11 +23,10 @@
 import android.support.v17.leanback.widget.ItemAlignmentFacet;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 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.TitleHelper;
 import android.support.v17.leanback.widget.TitleView;
@@ -43,8 +42,7 @@
  *
  * <p>
  * A DetailsSupportFragment 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}, the Adapter's {@link PresenterSelector} must maintains subclasses
+ * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
  * of {@link RowPresenter}.
  * </p>
  *
@@ -94,18 +92,18 @@
 
     private ObjectAdapter mAdapter;
     private int mContainerListAlignTop;
-    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private BaseOnItemViewClickedListener mOnItemViewClickedListener;
 
     private Object mSceneAfterEntranceTransition;
 
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
 
-    private final OnItemViewSelectedListener mOnItemViewSelectedListener =
-            new OnItemViewSelectedListener() {
+    private final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener<Object>() {
         @Override
         public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
+                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
             int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
             int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
             if (DEBUG) Log.v(TAG, "row selected position " + position
@@ -146,14 +144,14 @@
     /**
      * Sets an item selection listener.
      */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
         mExternalOnItemViewSelectedListener = listener;
     }
 
     /**
      * Sets an item clicked listener.
      */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
         if (mOnItemViewClickedListener != listener) {
             mOnItemViewClickedListener = listener;
             if (mRowsSupportFragment != null) {
@@ -165,7 +163,7 @@
     /**
      * Returns the item clicked listener.
      */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
         return mOnItemViewClickedListener;
     }
 
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 a004307..11cfe84 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -22,15 +22,14 @@
 import android.os.Bundle;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 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.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnLayoutChangeListener;
@@ -256,7 +255,7 @@
     }
 
     @Override
-    void onTransitionStart() {
+    public void onTransitionStart() {
         super.onTransitionStart();
         if (!mHeadersEnabled) {
             // When enabling headers fragment,  the RowHeaderView gets a focus but
@@ -275,7 +274,7 @@
     }
 
     @Override
-    void onTransitionEnd() {
+    public void onTransitionEnd() {
         if (mHeadersEnabled) {
             final VerticalGridView listView = getVerticalGridView();
             if (listView != null) {
@@ -287,4 +286,9 @@
         }
         super.onTransitionEnd();
     }
+
+    public boolean isScrolling() {
+        return getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
index a3b0f44..bb198fa 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -24,15 +24,14 @@
 import android.os.Bundle;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HorizontalGridView;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 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.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnLayoutChangeListener;
@@ -258,7 +257,7 @@
     }
 
     @Override
-    void onTransitionStart() {
+    public void onTransitionStart() {
         super.onTransitionStart();
         if (!mHeadersEnabled) {
             // When enabling headers fragment,  the RowHeaderView gets a focus but
@@ -277,7 +276,7 @@
     }
 
     @Override
-    void onTransitionEnd() {
+    public void onTransitionEnd() {
         if (mHeadersEnabled) {
             final VerticalGridView listView = getVerticalGridView();
             if (listView != null) {
@@ -289,4 +288,9 @@
         }
         super.onTransitionEnd();
     }
+
+    public boolean isScrolling() {
+        return getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
index 76a6c33..9f5f650 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
@@ -79,7 +79,7 @@
  * </ul>
  * <p>
  * Note that the information is used in {@link #onCreateView}, so should be initialized before
- * calling {@code super.onCreateView} or in {@link Fragment#onAttach(android.app.Activity)}.
+ * calling {@code super.onCreateView}.
  * <p>
  * <h3>Animation</h3>
  * Onboarding screen has three kinds of animations:
@@ -135,7 +135,14 @@
  * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
  * need to set the onboardingTheme attribute; if set, it will be ignored.)
  *
- * @hide
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
  */
 abstract public class OnboardingFragment extends Fragment {
     private static final String TAG = "OnboardingFragment";
@@ -492,7 +499,7 @@
      *
      * @return The title of the page.
      */
-    abstract protected String getPageTitle(int pageIndex);
+    abstract protected CharSequence getPageTitle(int pageIndex);
 
     /**
      * Returns the description of the given page.
@@ -501,7 +508,7 @@
      *
      * @return The description of the page.
      */
-    abstract protected String getPageDescription(int pageIndex);
+    abstract protected CharSequence getPageDescription(int pageIndex);
 
     /**
      * Returns the index of the current page.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
index 4b26ced..dac7c26 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -81,7 +81,7 @@
  * </ul>
  * <p>
  * Note that the information is used in {@link #onCreateView}, so should be initialized before
- * calling {@code super.onCreateView} or in {@link Fragment#onAttach(android.support.v4.app.FragmentActivity)}.
+ * calling {@code super.onCreateView}.
  * <p>
  * <h3>Animation</h3>
  * Onboarding screen has three kinds of animations:
@@ -137,7 +137,14 @@
  * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
  * need to set the onboardingTheme attribute; if set, it will be ignored.)
  *
- * @hide
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
  */
 abstract public class OnboardingSupportFragment extends Fragment {
     private static final String TAG = "OnboardingSupportFragment";
@@ -494,7 +501,7 @@
      *
      * @return The title of the page.
      */
-    abstract protected String getPageTitle(int pageIndex);
+    abstract protected CharSequence getPageTitle(int pageIndex);
 
     /**
      * Returns the description of the given page.
@@ -503,7 +510,7 @@
      *
      * @return The description of the page.
      */
-    abstract protected String getPageDescription(int pageIndex);
+    abstract protected CharSequence getPageDescription(int pageIndex);
 
     /**
      * Returns the index of the current page.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java
new file mode 100644
index 0000000..ee9f866
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.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.v17.leanback.app;
+
+import android.os.Build;
+
+/**
+ * @hide
+ */
+public class PermissionHelper {
+
+    public static void requestPermissions(android.app.Fragment fragment, String[] permissions,
+            int requestCode) {
+        if (Build.VERSION.SDK_INT >= 23) {
+            PermissionHelper23.requestPermissions(fragment, permissions, requestCode);
+        }
+    }
+
+    public static void requestPermissions(android.support.v4.app.Fragment fragment,
+            String[] permissions, int requestCode) {
+        fragment.requestPermissions(permissions, requestCode);
+    }
+
+}
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 46e91c8..2bbada1 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -21,7 +21,6 @@
 import android.animation.ValueAnimator;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Row;
 import android.view.InputEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -33,12 +32,14 @@
 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.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.VerticalGridView;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -52,8 +53,8 @@
  * A fragment for displaying playback controls and related content.
  * <p>
  * A PlaybackOverlayFragment 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}.
+ * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
  * </p>
  * <p>
  * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
@@ -295,6 +296,17 @@
         }
     }
 
+    /**
+     * Fades out the playback overlay immediately.
+     */
+    public void fadeOut() {
+        if (!mFadingEnabled) {
+            return;
+        }
+        mHandler.removeMessages(START_FADE_OUT);
+        fade(false);
+    }
+
     private boolean areControlsHidden() {
         return mFadingStatus == IDLE && mBgAlpha == 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
index 58433ef..a7fcc40 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -23,7 +23,6 @@
 import android.animation.ValueAnimator;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Row;
 import android.view.InputEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -35,12 +34,14 @@
 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.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.VerticalGridView;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -54,8 +55,8 @@
  * A fragment for displaying playback controls and related content.
  * <p>
  * A PlaybackOverlaySupportFragment 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}.
+ * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
  * </p>
  * <p>
  * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
@@ -297,6 +298,17 @@
         }
     }
 
+    /**
+     * Fades out the playback overlay immediately.
+     */
+    public void fadeOut() {
+        if (!mFadingEnabled) {
+            return;
+        }
+        mHandler.removeMessages(START_FADE_OUT);
+        fade(false);
+    }
+
     private boolean areControlsHidden() {
         return mFadingStatus == IDLE && mBgAlpha == 0;
     }
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 724c139..2294903 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -13,42 +13,56 @@
  */
 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.HorizontalGridView;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 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.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.ViewHolderTask;
-import android.support.v17.leanback.widget.HorizontalGridView;
-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;
 
+import java.util.ArrayList;
+
 /**
  * An ordered set of rows of leanback widgets.
  * <p>
  * A RowsFragment renders the elements of its
  * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link android.support.v17.leanback.widget.Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
  * </p>
  */
-public class RowsFragment extends BaseRowFragment {
+public class RowsFragment extends BaseRowFragment implements Adaptable {
+
+    final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
+    final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+
+    @Override
+    public Object getAdapter(Class clazz) {
+        if (clazz == BrowseFragment.MainFragmentAdapter.class) {
+            return mMainFragmentAdapter;
+        } else if (clazz == BrowseFragment.MainFragmentRowsAdapter.class) {
+            return mMainFragmentRowsAdapter;
+        }
+        return null;
+    }
 
     /**
      * Internal helper class that manages row select animation and apply a default
@@ -89,7 +103,7 @@
             if (mSelectAnimatorInterpolatorInUse != null) {
                 fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
             }
-            float level =  mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
             mRowPresenter.setSelectLevel(mRowViewHolder, level);
         }
 
@@ -116,14 +130,11 @@
     private int mSubPosition;
     private boolean mExpand = true;
     private boolean mViewsCreated;
-    private float mRowScaleFactor;
     private int mAlignedTop;
-    private boolean mRowScaleEnabled;
-    private ScaleFrameLayout mScaleFrameLayout;
     private boolean mAfterEntranceTransition = true;
 
-    private OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+    private BaseOnItemViewClickedListener mOnItemViewClickedListener;
 
     // Select animation and interpolator are not intended to be
     // exposed at this moment. They might be synced with vertical scroll
@@ -145,9 +156,9 @@
      * 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.
+     * So in general, developer should choose one of the listeners but not both.
      */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
         if (mViewsCreated) {
             throw new IllegalStateException(
@@ -158,23 +169,31 @@
     /**
      * Returns the item clicked listener.
      */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
         return mOnItemViewClickedListener;
     }
 
     /**
+     * @deprecated use {@link BrowseFragment#enableRowScaling(boolean)} instead.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableRowScaling(boolean enable) {
+    }
+
+    /**
      * 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);
+                ItemBridgeAdapter.ViewHolder vh
+                        = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
                 setRowViewExpanded(vh, mExpand);
             }
         }
@@ -183,7 +202,7 @@
     /**
      * Sets an item selection listener.
      */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
         mOnItemViewSelectedListener = listener;
         VerticalGridView listView = getVerticalGridView();
         if (listView != null) {
@@ -200,19 +219,10 @@
     /**
      * Returns an item selection listener.
      */
-    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+    public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
         return mOnItemViewSelectedListener;
     }
 
-    /**
-     * Enables scaling of rows.
-     *
-     * @param enable true to enable row scaling
-     */
-    public void enableRowScaling(boolean enable) {
-        mRowScaleEnabled = enable;
-    }
-
     @Override
     void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
             int position, int subposition) {
@@ -233,6 +243,7 @@
     /**
      * Get row ViewHolder at adapter position.  Returns null if the row object is not in adapter or
      * the row object has not been bound to a row view.
+     *
      * @param position Position of row in adapter.
      * @return Row ViewHolder at a given adapter position.
      */
@@ -255,16 +266,6 @@
         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
@@ -276,6 +277,8 @@
         getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
         getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
 
+        setAlignment(mAlignedTop);
+
         mRecycledViewPool = null;
         mPresenterMapper = null;
     }
@@ -286,35 +289,10 @@
         super.onDestroyView();
     }
 
-    @Override
-    void setItemAlignment() {
-        super.setItemAlignment();
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
-        }
-    }
-
     void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
         mExternalAdapterListener = listener;
     }
 
-    /**
-     * Returns the view that will change scale.
-     */
-    View getScaleView() {
-        return getVerticalGridView();
-    }
-
-    /**
-     * Sets the 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);
     }
@@ -334,6 +312,7 @@
                 mExternalAdapterListener.onAddPresenter(presenter, type);
             }
         }
+
         @Override
         public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
             VerticalGridView listView = getVerticalGridView();
@@ -352,6 +331,7 @@
                 mExternalAdapterListener.onCreate(vh);
             }
         }
+
         @Override
         public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
             if (DEBUG) Log.v(TAG, "onAttachToWindow");
@@ -370,6 +350,7 @@
                 mExternalAdapterListener.onAttachedToWindow(vh);
             }
         }
+
         @Override
         public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
             if (mSelectedViewHolder == vh) {
@@ -380,12 +361,14 @@
                 mExternalAdapterListener.onDetachedFromWindow(vh);
             }
         }
+
         @Override
         public void onBind(ItemBridgeAdapter.ViewHolder vh) {
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onBind(vh);
             }
         }
+
         @Override
         public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
             setRowViewSelected(vh, false, true);
@@ -431,7 +414,7 @@
     }
 
     @Override
-    boolean onTransitionPrepare() {
+    public boolean onTransitionPrepare() {
         boolean prepared = super.onTransitionPrepare();
         if (prepared) {
             freezeRows(true);
@@ -439,92 +422,8 @@
         return prepared;
     }
 
-    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 (getView() == null || getActivity() == null) {
-                mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
-                return true;
-            }
-            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) {
-        onTransitionPrepare();
-        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() {
+    public void onTransitionEnd() {
         super.onTransitionEnd();
         freezeRows(false);
     }
@@ -535,7 +434,7 @@
             final int count = verticalView.getChildCount();
             for (int i = 0; i < count; i++) {
                 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
                 RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
                 RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
                 rowPresenter.freeze(vh, freeze);
@@ -547,14 +446,14 @@
      * 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) {
+    public 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));
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
                 RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
                 RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
                 rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
@@ -602,4 +501,116 @@
         RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
         return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
     }
+
+    public boolean isScrolling() {
+        if (getVerticalGridView() == null) {
+            return false;
+        }
+        return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    @Override
+    public void setAlignment(int windowAlignOffsetFromTop) {
+        mAlignedTop = windowAlignOffsetFromTop;
+        final VerticalGridView gridView = getVerticalGridView();
+
+        if (gridView != null) {
+            gridView.setItemAlignmentOffset(0);
+            gridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setItemAlignmentOffsetWithPadding(true);
+            gridView.setWindowAlignmentOffset(mAlignedTop);
+            // align to a fixed position from top
+            gridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+
+    public static class MainFragmentAdapter extends BrowseFragment.MainFragmentAdapter<RowsFragment> {
+
+        public MainFragmentAdapter(RowsFragment fragment) {
+            super(fragment);
+            setScalingEnabled(true);
+        }
+
+        @Override
+        public boolean isScrolling() {
+            return getFragment().isScrolling();
+        }
+
+        @Override
+        public void setExpand(boolean expand) {
+            getFragment().setExpand(expand);
+        }
+
+        @Override
+        public void setEntranceTransitionState(boolean state) {
+            getFragment().setEntranceTransitionState(state);
+        }
+
+        @Override
+        public void setAlignment(int windowAlignOffsetFromTop) {
+            getFragment().setAlignment(windowAlignOffsetFromTop);
+        }
+
+        @Override
+        public boolean onTransitionPrepare() {
+            return getFragment().onTransitionPrepare();
+        }
+
+        @Override
+        public void onTransitionStart() {
+            getFragment().onTransitionStart();
+        }
+
+        @Override
+        public void onTransitionEnd() {
+            getFragment().onTransitionEnd();
+        }
+
+    }
+
+    public static class MainFragmentRowsAdapter
+            extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
+
+        public MainFragmentRowsAdapter(RowsFragment fragment) {
+            super(fragment);
+        }
+
+        @Override
+        public void setAdapter(ObjectAdapter adapter) {
+            getFragment().setAdapter(adapter);
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        @Override
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+            getFragment().setOnItemViewClickedListener(listener);
+        }
+
+        @Override
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+            getFragment().setOnItemViewSelectedListener(listener);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+            getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+            getFragment().setSelectedPosition(rowPosition, smooth);
+        }
+
+        @Override
+        public int getSelectedPosition() {
+            return getFragment().getSelectedPosition();
+        }
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index 2c9c719..197aad2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -15,42 +15,56 @@
  */
 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.HorizontalGridView;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 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.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.ViewHolderTask;
-import android.support.v17.leanback.widget.HorizontalGridView;
-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;
 
+import java.util.ArrayList;
+
 /**
  * An ordered set of rows of leanback widgets.
  * <p>
  * A RowsSupportFragment renders the elements of its
  * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link android.support.v17.leanback.widget.Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
  * </p>
  */
-public class RowsSupportFragment extends BaseRowSupportFragment {
+public class RowsSupportFragment extends BaseRowSupportFragment implements Adaptable {
+
+    final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
+    final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+
+    @Override
+    public Object getAdapter(Class clazz) {
+        if (clazz == BrowseSupportFragment.MainFragmentAdapter.class) {
+            return mMainFragmentAdapter;
+        } else if (clazz == BrowseSupportFragment.MainFragmentRowsAdapter.class) {
+            return mMainFragmentRowsAdapter;
+        }
+        return null;
+    }
 
     /**
      * Internal helper class that manages row select animation and apply a default
@@ -91,7 +105,7 @@
             if (mSelectAnimatorInterpolatorInUse != null) {
                 fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
             }
-            float level =  mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
             mRowPresenter.setSelectLevel(mRowViewHolder, level);
         }
 
@@ -118,14 +132,11 @@
     private int mSubPosition;
     private boolean mExpand = true;
     private boolean mViewsCreated;
-    private float mRowScaleFactor;
     private int mAlignedTop;
-    private boolean mRowScaleEnabled;
-    private ScaleFrameLayout mScaleFrameLayout;
     private boolean mAfterEntranceTransition = true;
 
-    private OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+    private BaseOnItemViewClickedListener mOnItemViewClickedListener;
 
     // Select animation and interpolator are not intended to be
     // exposed at this moment. They might be synced with vertical scroll
@@ -147,9 +158,9 @@
      * 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.
+     * So in general, developer should choose one of the listeners but not both.
      */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
         if (mViewsCreated) {
             throw new IllegalStateException(
@@ -160,23 +171,31 @@
     /**
      * Returns the item clicked listener.
      */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
         return mOnItemViewClickedListener;
     }
 
     /**
+     * @deprecated use {@link BrowseSupportFragment#enableRowScaling(boolean)} instead.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableRowScaling(boolean enable) {
+    }
+
+    /**
      * 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);
+                ItemBridgeAdapter.ViewHolder vh
+                        = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
                 setRowViewExpanded(vh, mExpand);
             }
         }
@@ -185,7 +204,7 @@
     /**
      * Sets an item selection listener.
      */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
         mOnItemViewSelectedListener = listener;
         VerticalGridView listView = getVerticalGridView();
         if (listView != null) {
@@ -202,19 +221,10 @@
     /**
      * Returns an item selection listener.
      */
-    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+    public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
         return mOnItemViewSelectedListener;
     }
 
-    /**
-     * Enables scaling of rows.
-     *
-     * @param enable true to enable row scaling
-     */
-    public void enableRowScaling(boolean enable) {
-        mRowScaleEnabled = enable;
-    }
-
     @Override
     void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
             int position, int subposition) {
@@ -235,6 +245,7 @@
     /**
      * Get row ViewHolder at adapter position.  Returns null if the row object is not in adapter or
      * the row object has not been bound to a row view.
+     *
      * @param position Position of row in adapter.
      * @return Row ViewHolder at a given adapter position.
      */
@@ -257,16 +268,6 @@
         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
@@ -278,6 +279,8 @@
         getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
         getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
 
+        setAlignment(mAlignedTop);
+
         mRecycledViewPool = null;
         mPresenterMapper = null;
     }
@@ -288,35 +291,10 @@
         super.onDestroyView();
     }
 
-    @Override
-    void setItemAlignment() {
-        super.setItemAlignment();
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
-        }
-    }
-
     void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
         mExternalAdapterListener = listener;
     }
 
-    /**
-     * Returns the view that will change scale.
-     */
-    View getScaleView() {
-        return getVerticalGridView();
-    }
-
-    /**
-     * Sets the 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);
     }
@@ -336,6 +314,7 @@
                 mExternalAdapterListener.onAddPresenter(presenter, type);
             }
         }
+
         @Override
         public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
             VerticalGridView listView = getVerticalGridView();
@@ -354,6 +333,7 @@
                 mExternalAdapterListener.onCreate(vh);
             }
         }
+
         @Override
         public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
             if (DEBUG) Log.v(TAG, "onAttachToWindow");
@@ -372,6 +352,7 @@
                 mExternalAdapterListener.onAttachedToWindow(vh);
             }
         }
+
         @Override
         public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
             if (mSelectedViewHolder == vh) {
@@ -382,12 +363,14 @@
                 mExternalAdapterListener.onDetachedFromWindow(vh);
             }
         }
+
         @Override
         public void onBind(ItemBridgeAdapter.ViewHolder vh) {
             if (mExternalAdapterListener != null) {
                 mExternalAdapterListener.onBind(vh);
             }
         }
+
         @Override
         public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
             setRowViewSelected(vh, false, true);
@@ -433,7 +416,7 @@
     }
 
     @Override
-    boolean onTransitionPrepare() {
+    public boolean onTransitionPrepare() {
         boolean prepared = super.onTransitionPrepare();
         if (prepared) {
             freezeRows(true);
@@ -441,92 +424,8 @@
         return prepared;
     }
 
-    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 (getView() == null || getActivity() == null) {
-                mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
-                return true;
-            }
-            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) {
-        onTransitionPrepare();
-        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() {
+    public void onTransitionEnd() {
         super.onTransitionEnd();
         freezeRows(false);
     }
@@ -537,7 +436,7 @@
             final int count = verticalView.getChildCount();
             for (int i = 0; i < count; i++) {
                 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
                 RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
                 RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
                 rowPresenter.freeze(vh, freeze);
@@ -549,14 +448,14 @@
      * 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) {
+    public 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));
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
                 RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
                 RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
                 rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
@@ -604,4 +503,116 @@
         RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
         return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
     }
+
+    public boolean isScrolling() {
+        if (getVerticalGridView() == null) {
+            return false;
+        }
+        return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    @Override
+    public void setAlignment(int windowAlignOffsetFromTop) {
+        mAlignedTop = windowAlignOffsetFromTop;
+        final VerticalGridView gridView = getVerticalGridView();
+
+        if (gridView != null) {
+            gridView.setItemAlignmentOffset(0);
+            gridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setItemAlignmentOffsetWithPadding(true);
+            gridView.setWindowAlignmentOffset(mAlignedTop);
+            // align to a fixed position from top
+            gridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+
+    public static class MainFragmentAdapter extends BrowseSupportFragment.MainFragmentAdapter<RowsSupportFragment> {
+
+        public MainFragmentAdapter(RowsSupportFragment fragment) {
+            super(fragment);
+            setScalingEnabled(true);
+        }
+
+        @Override
+        public boolean isScrolling() {
+            return getFragment().isScrolling();
+        }
+
+        @Override
+        public void setExpand(boolean expand) {
+            getFragment().setExpand(expand);
+        }
+
+        @Override
+        public void setEntranceTransitionState(boolean state) {
+            getFragment().setEntranceTransitionState(state);
+        }
+
+        @Override
+        public void setAlignment(int windowAlignOffsetFromTop) {
+            getFragment().setAlignment(windowAlignOffsetFromTop);
+        }
+
+        @Override
+        public boolean onTransitionPrepare() {
+            return getFragment().onTransitionPrepare();
+        }
+
+        @Override
+        public void onTransitionStart() {
+            getFragment().onTransitionStart();
+        }
+
+        @Override
+        public void onTransitionEnd() {
+            getFragment().onTransitionEnd();
+        }
+
+    }
+
+    public static class MainFragmentRowsAdapter
+            extends BrowseSupportFragment.MainFragmentRowsAdapter<RowsSupportFragment> {
+
+        public MainFragmentRowsAdapter(RowsSupportFragment fragment) {
+            super(fragment);
+        }
+
+        @Override
+        public void setAdapter(ObjectAdapter adapter) {
+            getFragment().setAdapter(adapter);
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        @Override
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+            getFragment().setOnItemViewClickedListener(listener);
+        }
+
+        @Override
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+            getFragment().setOnItemViewSelectedListener(listener);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+            getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+            getFragment().setSelectedPosition(rowPosition, smooth);
+        }
+
+        @Override
+        public int getSelectedPosition() {
+            return getFragment().getSelectedPosition();
+        }
+    }
 }
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 17e5a9c..ea9e138 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -13,6 +13,7 @@
  */
 package android.support.v17.leanback.app;
 
+import android.Manifest;
 import android.app.Fragment;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -41,6 +42,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 /**
  * A fragment to handle searches. An application will supply an implementation
  * of the {@link SearchResultProvider} interface to handle the search and return
@@ -50,8 +53,10 @@
  *
  * <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.
+ * recognizer will be used for which your application will need to declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
+ * the device version is >= 23, a permission dialog will show first time using speech recognition.
+ * 0 will be used as requestCode in requestPermissions() call.
  * </p>
  * <p>
  * Speech recognition is automatically started when fragment is created, but
@@ -73,6 +78,8 @@
     private static final int RESULTS_CHANGED = 0x1;
     private static final int QUERY_COMPLETE = 0x2;
 
+    private static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
+
     /**
      * Search API to be provided by the application.
      */
@@ -214,6 +221,26 @@
     private int mStatus;
     private boolean mAutoStartRecognition = true;
 
+    private boolean mIsPaused;
+    private boolean mPendingStartRecognitionWhenPaused;
+    private SearchBar.SearchBarPermissionListener mPermissionListener
+            = new SearchBar.SearchBarPermissionListener() {
+        public void requestAudioPermission() {
+            PermissionHelper.requestPermissions(SearchFragment.this,
+                    new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
+        }
+    };
+
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                                           int[] grantResults) {
+        if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
+            if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
+                    && grantResults[0] == PERMISSION_GRANTED) {
+                startRecognition();
+            }
+        }
+    }
+
     /**
      * @param args Bundle to use for the arguments, if null a new Bundle will be created.
      */
@@ -286,6 +313,7 @@
             }
         });
         mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        mSearchBar.setPermissionListener(mPermissionListener);
         applyExternalQuery();
 
         readArguments(getArguments());
@@ -355,17 +383,24 @@
     @Override
     public void onResume() {
         super.onResume();
+        mIsPaused = false;
         if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
             mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
             mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
         }
-        // Ensure search bar state consistency when using external recognizer
-        mSearchBar.stopRecognition();
+        if (mPendingStartRecognitionWhenPaused) {
+            mPendingStartRecognitionWhenPaused = false;
+            mSearchBar.startRecognition();
+        } else {
+            // Ensure search bar state consistency when using external recognizer
+            mSearchBar.stopRecognition();
+        }
     }
 
     @Override
     public void onPause() {
         releaseRecognizer();
+        mIsPaused = true;
         super.onPause();
     }
 
@@ -391,7 +426,11 @@
      * when fragment is created.
      */
     public void startRecognition() {
-        mSearchBar.startRecognition();
+        if (mIsPaused) {
+            mPendingStartRecognitionWhenPaused = true;
+        } else {
+            mSearchBar.startRecognition();
+        }
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
index abfbd15..0368339 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -15,6 +15,7 @@
  */
 package android.support.v17.leanback.app;
 
+import android.Manifest;
 import android.support.v4.app.Fragment;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -43,6 +44,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 /**
  * A fragment to handle searches. An application will supply an implementation
  * of the {@link SearchResultProvider} interface to handle the search and return
@@ -52,8 +55,10 @@
  *
  * <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.
+ * recognizer will be used for which your application will need to declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
+ * the device version is >= 23, a permission dialog will show first time using speech recognition.
+ * 0 will be used as requestCode in requestPermissions() call.
  * </p>
  * <p>
  * Speech recognition is automatically started when fragment is created, but
@@ -75,6 +80,8 @@
     private static final int RESULTS_CHANGED = 0x1;
     private static final int QUERY_COMPLETE = 0x2;
 
+    private static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
+
     /**
      * Search API to be provided by the application.
      */
@@ -216,6 +223,26 @@
     private int mStatus;
     private boolean mAutoStartRecognition = true;
 
+    private boolean mIsPaused;
+    private boolean mPendingStartRecognitionWhenPaused;
+    private SearchBar.SearchBarPermissionListener mPermissionListener
+            = new SearchBar.SearchBarPermissionListener() {
+        public void requestAudioPermission() {
+            PermissionHelper.requestPermissions(SearchSupportFragment.this,
+                    new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
+        }
+    };
+
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                                           int[] grantResults) {
+        if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
+            if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
+                    && grantResults[0] == PERMISSION_GRANTED) {
+                startRecognition();
+            }
+        }
+    }
+
     /**
      * @param args Bundle to use for the arguments, if null a new Bundle will be created.
      */
@@ -288,6 +315,7 @@
             }
         });
         mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        mSearchBar.setPermissionListener(mPermissionListener);
         applyExternalQuery();
 
         readArguments(getArguments());
@@ -357,17 +385,24 @@
     @Override
     public void onResume() {
         super.onResume();
+        mIsPaused = false;
         if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
             mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
             mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
         }
-        // Ensure search bar state consistency when using external recognizer
-        mSearchBar.stopRecognition();
+        if (mPendingStartRecognitionWhenPaused) {
+            mPendingStartRecognitionWhenPaused = false;
+            mSearchBar.startRecognition();
+        } else {
+            // Ensure search bar state consistency when using external recognizer
+            mSearchBar.stopRecognition();
+        }
     }
 
     @Override
     public void onPause() {
         releaseRecognizer();
+        mIsPaused = true;
         super.onPause();
     }
 
@@ -393,7 +428,11 @@
      * when fragment is created.
      */
     public void startRecognition() {
-        mSearchBar.startRecognition();
+        if (mIsPaused) {
+            mPendingStartRecognitionWhenPaused = true;
+        } else {
+            mSearchBar.startRecognition();
+        }
     }
 
     /**
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 3e51989..cd3d80b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -13,7 +13,6 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.annotation.ColorInt;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.BrowseFrameLayout;
@@ -23,7 +22,6 @@
 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.TitleHelper;
 import android.support.v17.leanback.widget.TitleView;
 import android.support.v17.leanback.widget.VerticalGridPresenter;
 import android.support.v17.leanback.widget.ObjectAdapter;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index eb0b337..ccae02f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -15,7 +15,6 @@
  */
 package android.support.v17.leanback.app;
 
-import android.support.annotation.ColorInt;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.BrowseFrameLayout;
@@ -25,7 +24,6 @@
 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.TitleHelper;
 import android.support.v17.leanback.widget.TitleView;
 import android.support.v17.leanback.widget.VerticalGridPresenter;
 import android.support.v17.leanback.widget.ObjectAdapter;
diff --git a/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
new file mode 100644
index 0000000..4155af6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Linear or DAG of {@link State}s. StateMachine is by default a linear model, until
+ * {@link #addState(State, State)} is called.  Each State has three status:
+ * STATUS_ZERO, STATUS_INVOKED, STATUS_EXECUTED.   We allow client to run a State, which will
+ * put State in STATUS_INVOKED.  A State will be executed when prior States are executed and
+ * Precondition for this State is true, then the State will be marked as STATUS_EXECUTED.
+ *
+ * @hide
+ */
+public final class StateMachine {
+
+    /**
+     * No request on the State
+     */
+    public static final int STATUS_ZERO = 0;
+    /**
+     * Somebody wants to run the state but not yet executed because either the condition is
+     * false or lower States are not executed.
+     */
+    public static final int STATUS_INVOKED = 1;
+    /**
+     * Somebody wants to run the State and the State was executed.
+     */
+    public static final int STATUS_EXECUTED = 2;
+
+    public static class State {
+
+        private int mStatus;
+        private ArrayList<State> mPriorStates;
+
+        /**
+         * Run State, Subclass may override.
+         */
+        public void run() {
+        }
+
+        /**
+         * Returns true if State can run, false otherwise.  Subclass may override.
+         * @return True if State can run, false otherwise.  Subclass may override.
+         */
+        public boolean canRun() {
+            return true;
+        }
+
+        /**
+         * @return True if the State has been executed.
+         */
+        final boolean runIfNeeded() {
+            if (mStatus!= STATUS_EXECUTED) {
+                if (mStatus == STATUS_INVOKED && canRun()) {
+                    run();
+                    mStatus = STATUS_EXECUTED;
+                } else {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        void addPriorState(State state) {
+            if (mPriorStates == null) {
+                mPriorStates = new ArrayList<State>();
+            }
+            if (!mPriorStates.contains(state)) {
+                mPriorStates.add(state);
+            }
+        }
+
+        final void markInvoked() {
+            if (mStatus == STATUS_ZERO) {
+                mStatus = STATUS_INVOKED;
+            }
+        }
+
+        final void updateStatus(int status) {
+            mStatus = status;
+        }
+
+        /**
+         * Get status, return one of {@link #STATUS_ZERO}, {@link #STATUS_INVOKED},
+         * {@link #STATUS_EXECUTED}.
+         * @return Status of the State.
+         */
+        public final int getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public final boolean equals(Object other) {
+            return this == other;
+        }
+    }
+
+    private boolean mSorted = true;
+    private final ArrayList<State> mSortedList = new ArrayList<State>();
+
+    /**
+     * Add a State to StateMachine, ignore if it is already added.
+     * @param state The state to add.
+     */
+    public void addState(State state) {
+        if (!mSortedList.contains(state)) {
+            state.updateStatus(STATUS_ZERO);
+            mSortedList.add(state);
+        }
+    }
+
+    /**
+     * Add two States to StateMachine and create an edge between this two.
+     * StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
+     * sort() is required to sort the Direct acyclic graph.
+     * @param fromState The from state to add.
+     * @param toState The to state to add.
+     */
+    public void addState(State fromState, State toState) {
+        addState(fromState);
+        addState(toState);
+        toState.addPriorState(fromState);
+        mSorted = false;
+    }
+
+    void verifySorted() {
+        if (!mSorted) {
+            throw new RuntimeException("Graph not sorted");
+        }
+    }
+
+    public void runState(State state) {
+        verifySorted();
+        state.markInvoked();
+        runPendingStates();
+    }
+
+    public void runPendingStates() {
+        verifySorted();
+        for (int i = 0, size = mSortedList.size(); i < size; i++) {
+            if (!mSortedList.get(i).runIfNeeded()) {
+                break;
+            }
+        }
+    }
+
+    public void resetStatus() {
+        for (int i = 0, size = mSortedList.size(); i < size; i++) {
+            mSortedList.get(i).updateStatus(STATUS_ZERO);
+        }
+    }
+
+    /**
+     * StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
+     * sort() is required to sort the Direct acyclic graph.
+     */
+    public void sort() {
+        if (mSorted) {
+            return;
+        }
+        // L: Empty list that will contain the sorted States
+        ArrayList<State> L = new ArrayList<State>();
+        // S: Set of all nodes with no incoming edges
+        ArrayList<State> S = new ArrayList<State>();
+        HashMap<State, ArrayList<State>> edges = new HashMap<State, ArrayList<State>>();
+        for (int i = mSortedList.size() - 1; i >= 0 ; i--) {
+            State state = mSortedList.get(i);
+            if (state.mPriorStates != null && state.mPriorStates.size() > 0) {
+                edges.put(state, new ArrayList<State>(state.mPriorStates));
+            } else {
+                S.add(state);
+            }
+        }
+
+        while (!S.isEmpty()) {
+            // remove a State without incoming Node from S, add to L
+            State state = S.remove(S.size() - 1);
+            L.add(state);
+            // for each toState that having an incoming edge from "state":
+            for (Iterator<Map.Entry<State, ArrayList<State>>> iterator =
+                    edges.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry<State, ArrayList<State>> entry = iterator.next();
+                ArrayList<State> fromStates = entry.getValue();
+                // remove edge from graph
+                if (fromStates.remove(state)) {
+                    if (fromStates.size() == 0) {
+                        State toState = entry.getKey();
+                        // insert the toState to S if it has no more incoming edges
+                        S.add(toState);
+                        iterator.remove();
+                    }
+                }
+            }
+        }
+        if (edges.size() > 0) {
+            throw new RuntimeException("Cycle in Graph");
+        }
+
+        mSortedList.clear();
+        mSortedList.addAll(L);
+        mSorted = true;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
new file mode 100644
index 0000000..0613667
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when an item view holder is clicked.
+ */
+public interface BaseOnItemViewClickedListener<T> {
+
+    /**
+     * Called when an item inside a row gets clicked.
+     * @param itemViewHolder The view holder of the item that is clicked.
+     * @param item The item that is currently selected.
+     * @param rowViewHolder The view holder of the row which the clicked item belongs to.
+     * @param row The row which the clicked item belongs to.
+     */
+    public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                              RowPresenter.ViewHolder rowViewHolder, T row);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
new file mode 100644
index 0000000..b43e146
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when a row or item becomes selected. The concept of
+ * current selection is different than focus.  A row or item can be selected without having focus;
+ * for example, when a row header view gains focus then the corresponding row view becomes selected.
+ */
+public interface BaseOnItemViewSelectedListener<T> {
+
+    /**
+     * Called when a row or a new item becomes selected.
+     * <p>
+     * For a non {@link ListRow} case, parameter item may be null.  Event is fired when
+     * selection changes between rows, regardless if row view has focus or not.
+     * <p>
+     * For a {@link ListRow} case, parameter item is null if the list row is empty.
+     * </p>
+     * <p>
+     * In the case of a grid, the row parameter is always null.
+     * </p>
+     * <li>
+     * Row has focus: event is fired when focus changes between children of the row.
+     * </li>
+     * <li>
+     * No row has focus: the event is fired with the currently selected row and last
+     * focused item in the row.
+     * </li>
+     *
+     * @param itemViewHolder The view holder of the item that is currently selected.
+     * @param item The item that is currently selected.
+     * @param rowViewHolder The view holder of the row that is currently selected.
+     * @param row The row that is currently selected.
+     */
+    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                               RowPresenter.ViewHolder rowViewHolder, T row);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
index 7512dcd..c19c390 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
@@ -33,7 +33,7 @@
          * Returns the view where focus should be requested given the current focused view and
          * the direction of focus search.
          */
-        public View onFocusSearch(View focused, int direction);
+        View onFocusSearch(View focused, int direction);
     }
 
     /**
@@ -44,13 +44,13 @@
          * See {@link android.view.ViewGroup#onRequestFocusInDescendants(
          * int, android.graphics.Rect)}.
          */
-        public boolean onRequestFocusInDescendants(int direction,
+        boolean onRequestFocusInDescendants(int direction,
                 Rect previouslyFocusedRect);
         /**
          * See {@link android.view.ViewGroup#requestChildFocus(
          * android.view.View, android.view.View)}.
          */
-        public void onRequestChildFocus(View child, View focused);
+        void onRequestChildFocus(View child, View focused);
     }
 
     public BrowseFrameLayout(Context context) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
index 12782a3..30b3b89 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -115,18 +115,19 @@
         private CharSequence mDescription;
         private CharSequence mEditDescription;
         private Drawable mIcon;
-        private boolean mChecked;
-        private boolean mMultilineDescription;
-        private boolean mHasNext;
-        private boolean mInfoOnly;
+        /**
+         * The mActionFlags holds various action states such as whether title or description are
+         * editable, or the action is focusable.
+         *
+         */
+        private int mActionFlags;
+
         private int mEditable = EDITING_NONE;
         private int mInputType = InputType.TYPE_CLASS_TEXT;
         private int mDescriptionInputType = InputType.TYPE_CLASS_TEXT;
         private int mEditInputType = InputType.TYPE_CLASS_TEXT;
         private int mDescriptionEditInputType = InputType.TYPE_CLASS_TEXT;
         private int mCheckSetId = NO_CHECK_SET;
-        private boolean mEnabled = true;
-        private boolean mFocusable = true;
         private List<GuidedAction> mSubActions;
         private Intent mIntent;
 
@@ -136,6 +137,7 @@
          */
         public BuilderBase(Context context) {
             mContext = context;
+            mActionFlags = PF_ENABLED | PF_FOCUSABLE;
         }
 
         /**
@@ -146,6 +148,10 @@
             return mContext;
         }
 
+        private void setFlags(int flag, int mask) {
+            mActionFlags = (mActionFlags & ~mask) | (flag & mask);
+        }
+
         /**
          * Subclass of BuilderBase should call this function to apply values.
          * @param action GuidedAction to apply BuilderBase values.
@@ -166,13 +172,8 @@
             action.mDescriptionInputType = mDescriptionInputType;
             action.mEditInputType = mEditInputType;
             action.mDescriptionEditInputType = mDescriptionEditInputType;
-            action.mChecked = mChecked;
+            action.mActionFlags = mActionFlags;
             action.mCheckSetId = mCheckSetId;
-            action.mMultilineDescription = mMultilineDescription;
-            action.mHasNext = mHasNext;
-            action.mInfoOnly = mInfoOnly;
-            action.mEnabled = mEnabled;
-            action.mFocusable = mFocusable;
             action.mSubActions = mSubActions;
         }
 
@@ -354,7 +355,7 @@
                 return (B) this;
             }
             mEditable = EDITING_TITLE;
-            if (mChecked || mCheckSetId != NO_CHECK_SET) {
+            if (isChecked() || mCheckSetId != NO_CHECK_SET) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
             return (B) this;
@@ -372,7 +373,7 @@
                 return (B) this;
             }
             mEditable = EDITING_DESCRIPTION;
-            if (mChecked || mCheckSetId != NO_CHECK_SET) {
+            if (isChecked() || mCheckSetId != NO_CHECK_SET) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
             return (B) this;
@@ -390,7 +391,7 @@
                 return (B) this;
             }
             mEditable = EDITING_ACTIVATOR_VIEW;
-            if (mChecked || mCheckSetId != NO_CHECK_SET) {
+            if (isChecked() || mCheckSetId != NO_CHECK_SET) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
             return (B) this;
@@ -438,12 +439,15 @@
         }
 
 
+        private boolean isChecked() {
+            return (mActionFlags & PF_CHECKED) == PF_CHECKED;
+        }
         /**
          * Indicates whether this action is initially checked.
          * @param checked Whether this action is checked.
          */
         public B checked(boolean checked) {
-            mChecked = checked;
+            setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
             if (mEditable != EDITING_NONE) {
                 throw new IllegalArgumentException("Editable actions cannot also be checked");
             }
@@ -471,7 +475,8 @@
          * @param multilineDescription Whether this action has a multiline description.
          */
         public B multilineDescription(boolean multilineDescription) {
-            mMultilineDescription = multilineDescription;
+            setFlags(multilineDescription ? PF_MULTI_lINE_DESCRIPTION : 0,
+                    PF_MULTI_lINE_DESCRIPTION);
             return (B) this;
         }
 
@@ -480,7 +485,7 @@
          * @param hasNext Whether this action has a next state.
          */
         public B hasNext(boolean hasNext) {
-            mHasNext = hasNext;
+            setFlags(hasNext ? PF_HAS_NEXT : 0, PF_HAS_NEXT);
             return (B) this;
         }
 
@@ -489,7 +494,7 @@
          * @param infoOnly Whether this action has a next state.
          */
         public B infoOnly(boolean infoOnly) {
-            mInfoOnly = infoOnly;
+            setFlags(infoOnly ? PF_INFO_ONLY : 0, PF_INFO_ONLY);
             return (B) this;
         }
 
@@ -498,7 +503,7 @@
          * @param enabled Whether the action is enabled.
          */
         public B enabled(boolean enabled) {
-            mEnabled = enabled;
+            setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
             return (B) this;
         }
 
@@ -508,7 +513,7 @@
          * @return The same BuilderBase object.
          */
         public B focusable(boolean focusable) {
-            mFocusable = focusable;
+            setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
             return (B) this;
         }
 
@@ -556,6 +561,14 @@
 
     }
 
+    private static final int PF_CHECKED = 0x00000001;
+    private static final int PF_MULTI_lINE_DESCRIPTION = 0x00000002;
+    private static final int PF_HAS_NEXT = 0x00000004;
+    private static final int PF_INFO_ONLY = 0x00000008;
+    private static final int PF_ENABLED = 0x00000010;
+    private static final int PF_FOCUSABLE = 0x00000020;
+    private int mActionFlags;
+
     private CharSequence mEditTitle;
     private CharSequence mEditDescription;
     private int mEditable;
@@ -563,13 +576,9 @@
     private int mDescriptionInputType;
     private int mEditInputType;
     private int mDescriptionEditInputType;
-    private boolean mMultilineDescription;
-    private boolean mHasNext;
-    private boolean mChecked;
-    private boolean mInfoOnly;
+
     private int mCheckSetId;
-    private boolean mEnabled;
-    private boolean mFocusable;
+
     private List<GuidedAction> mSubActions;
 
     private Intent mIntent;
@@ -578,6 +587,10 @@
         super(0);
     }
 
+    private void setFlags(int flag, int mask) {
+        mActionFlags = (mActionFlags & ~mask) | (flag & mask);
+    }
+
     /**
      * Returns the title of this action.
      * @return The title set when this action was built.
@@ -740,7 +753,7 @@
      * @return true if the action is currently checked, false otherwise.
      */
     public boolean isChecked() {
-        return mChecked;
+        return (mActionFlags & PF_CHECKED) == PF_CHECKED;
     }
 
     /**
@@ -748,7 +761,7 @@
      * @param checked Whether this action should be checked.
      */
     public void setChecked(boolean checked) {
-        mChecked = checked;
+        setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
     }
 
     /**
@@ -770,7 +783,7 @@
      * otherwise.
      */
     public boolean hasMultilineDescription() {
-        return mMultilineDescription;
+        return (mActionFlags & PF_MULTI_lINE_DESCRIPTION) == PF_MULTI_lINE_DESCRIPTION;
     }
 
     /**
@@ -778,7 +791,7 @@
      * @return true if the action is currently enabled, false otherwise.
      */
     public boolean isEnabled() {
-        return mEnabled;
+        return (mActionFlags & PF_ENABLED) == PF_ENABLED;
     }
 
     /**
@@ -786,7 +799,7 @@
      * @param enabled Whether this action should be enabled.
      */
     public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
+        setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
     }
 
     /**
@@ -794,7 +807,7 @@
      * @return true if the action is currently focusable, false otherwise.
      */
     public boolean isFocusable() {
-        return mFocusable;
+        return (mActionFlags & PF_FOCUSABLE) == PF_FOCUSABLE;
     }
 
     /**
@@ -802,7 +815,7 @@
      * @param focusable Whether this action should be focusable.
      */
     public void setFocusable(boolean focusable) {
-        mFocusable = focusable;
+        setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
     }
 
     /**
@@ -811,7 +824,7 @@
      * @return true if the action will request further user input when selected, false otherwise.
      */
     public boolean hasNext() {
-        return mHasNext;
+        return (mActionFlags & PF_HAS_NEXT) == PF_HAS_NEXT;
     }
 
     /**
@@ -822,7 +835,7 @@
      * @return true if will only display information, false otherwise.
      */
     public boolean infoOnly() {
-        return mInfoOnly;
+        return (mActionFlags & PF_INFO_ONLY) == PF_INFO_ONLY;
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
index c5dc25c..f4bf37d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
@@ -14,11 +14,9 @@
 package android.support.v17.leanback.widget;
 
 /**
- * Interface for receiving notification when a item view holder is clicked.
+ * Interface for receiving notification when a item view holder is clicked.  This interface expects
+ * row object to be sub class of {@link Row}.
  */
-public interface OnItemViewClickedListener {
-
-    public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-            RowPresenter.ViewHolder rowViewHolder, Row row);
+public interface OnItemViewClickedListener extends BaseOnItemViewClickedListener<Row> {
 
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
index 5e355bf..30fdb67 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
@@ -17,32 +17,7 @@
  * Interface for receiving notification when a row or item becomes selected. The concept of
  * current selection is different than focus.  A row or item can be selected without having focus;
  * for example, when a row header view gains focus then the corresponding row view becomes selected.
+ * This interface expects row object to be sub class of {@link Row}.
  */
-public interface OnItemViewSelectedListener {
-    /**
-     * Called when the a row or a new item becomes selected.
-     * <p>
-     * For a non {@link ListRow} case, parameter item may be null.  Event is fired when
-     * selection changes between rows, regardless if row view has focus or not.
-     * <p>
-     * For a {@link ListRow} case, parameter item is null if the list row is empty.
-     * </p>
-     * <p>
-     * In the case of a grid, the row parameter is always null.
-     * </p>
-     * <li>
-     * Row has focus: event is fired when focus changes between children of the row.
-     * </li>
-     * <li>
-     * No row has focus: the event is fired with the currently selected row and last
-     * focused item in the row.
-     * </li>
-     *
-     * @param itemViewHolder The view holder of the item that is currently selected.
-     * @param item The item that is currently selected.
-     * @param rowViewHolder The view holder of the row that is currently selected.
-     * @param row The row that is currently selected.
-     */
-    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-            RowPresenter.ViewHolder rowViewHolder, Row row);
+public interface OnItemViewSelectedListener extends BaseOnItemViewSelectedListener<Row> {
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java
new file mode 100644
index 0000000..1ebfe2b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java
@@ -0,0 +1,24 @@
+/*
+ * 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.v17.leanback.widget;
+
+/**
+ * Used to represent content spanning full page.
+ */
+public class PageRow extends Row {
+
+    public PageRow(HeaderItem headerItem) {
+        super(headerItem);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PageRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PageRowPresenter.java
new file mode 100644
index 0000000..618f37d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PageRowPresenter.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.v17.leanback.widget;
+
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class PageRowPresenter extends RowPresenter {
+
+    public PageRowPresenter() {
+        setHeaderPresenter(null);
+    }
+
+    @Override
+    protected ViewHolder createRowViewHolder(ViewGroup parent) {
+        RelativeLayout root = new RelativeLayout(parent.getContext());
+        root.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
+        return new ViewHolder(root);
+    }
+}
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 60327f2..b1cf2f9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -321,6 +321,23 @@
     }
 
     /**
+     * An action displaying an icon for picture-in-picture.
+     */
+    public static class PictureInPictureAction extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public PictureInPictureAction(Context context) {
+            super(R.id.lb_control_picture_in_picture);
+            setIcon(getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_picture_in_picture));
+            setLabel1(context.getString(R.string.lb_playback_controls_picture_in_picture));
+            addKeyCode(KeyEvent.KEYCODE_WINDOW);
+        }
+    }
+
+    /**
      * An action displaying an icon for "more actions".
      */
     public static class MoreActions extends Action {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index cb1f2ac..1b57fc5 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -19,7 +19,9 @@
 import android.view.ViewGroup;
 
 /**
- * An abstract {@link Presenter} that renders a {@link Row}.
+ * An abstract {@link Presenter} that renders an Object in RowsFragment, the object can be
+ * subclass {@link Row} or a generic one.  When the object is not {@link Row} class,
+ * {@link ViewHolder#getRow()} returns null.
  *
  * <h3>Customize UI widgets</h3>
  * When a subclass of RowPresenter adds UI widgets, it should subclass
@@ -143,6 +145,7 @@
         ContainerViewHolder mContainerViewHolder;
         RowHeaderPresenter.ViewHolder mHeaderViewHolder;
         Row mRow;
+        Object mRowObject;
         int mActivated = ACTIVATED_NOT_ASSIGNED;
         boolean mSelected;
         boolean mExpanded;
@@ -150,8 +153,8 @@
         float mSelectLevel = 0f; // initially unselected
         protected final ColorOverlayDimmer mColorDimmer;
         private View.OnKeyListener mOnKeyListener;
-        private OnItemViewSelectedListener mOnItemViewSelectedListener;
-        private OnItemViewClickedListener mOnItemViewClickedListener;
+        private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+        private BaseOnItemViewClickedListener mOnItemViewClickedListener;
 
         /**
          * Constructor for ViewHolder.
@@ -164,13 +167,24 @@
         }
 
         /**
-         * Returns the Row bound to the View in this ViewHolder.
+         * Returns the row bound to this ViewHolder. Returns null if the row is not an instance of
+         * {@link Row}.
+         * @return The row bound to this ViewHolder. Returns null if the row is not an instance of
+         * {@link Row}.
          */
         public final Row getRow() {
             return mRow;
         }
 
         /**
+         * Returns the Row object bound to this ViewHolder.
+         * @return The row object bound to this ViewHolder.
+         */
+        public final Object getRowObject() {
+            return mRowObject;
+        }
+
+        /**
          * Returns whether the Row is in its expanded state.
          *
          * @return true if the Row is expanded, false otherwise.
@@ -249,14 +263,14 @@
          * event with null item.  A subclass of RowPresenter e.g. {@link ListRowPresenter} may
          * fire a selection event with selected item.
          */
-        public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        public final void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
             mOnItemViewSelectedListener = listener;
         }
 
         /**
          * Returns the listener for item or row selection.
          */
-        public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        public final BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
             return mOnItemViewSelectedListener;
         }
 
@@ -266,14 +280,14 @@
          * OnItemViewClickedListener will override {@link View.OnClickListener} that
          * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
          */
-        public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        public final void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
             mOnItemViewClickedListener = listener;
         }
 
         /**
          * Returns the listener for item click event.
          */
-        public final OnItemViewClickedListener getOnItemViewClickedListener() {
+        public final BaseOnItemViewClickedListener getOnItemViewClickedListener() {
             return mOnItemViewClickedListener;
         }
     }
@@ -467,7 +481,7 @@
     protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
         if (selected) {
             if (vh.mOnItemViewSelectedListener != null) {
-                vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
+                vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRowObject());
             }
         }
     }
@@ -574,8 +588,9 @@
      * Binds the given row object to the given ViewHolder.
      */
     protected void onBindRowViewHolder(ViewHolder vh, Object item) {
-        vh.mRow = (Row) item;
-        if (vh.mHeaderViewHolder != null) {
+        vh.mRowObject = item;
+        vh.mRow = item instanceof Row ? (Row) item : null;
+        if (vh.mHeaderViewHolder != null && vh.getRow() != null) {
             mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
         }
     }
@@ -593,6 +608,7 @@
             mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
         }
         vh.mRow = null;
+        vh.mRowObject = null;
     }
 
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
index 362744e..90616a2 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
@@ -18,6 +18,7 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 /**
@@ -31,6 +32,8 @@
     private float mLayoutScaleX = 1f;
     private float mLayoutScaleY = 1f;
 
+    private float mChildScale = 1f;
+
     public ScaleFrameLayout(Context context) {
         this(context ,null);
     }
@@ -58,6 +61,34 @@
         }
     }
 
+    public void setChildScale(float scale) {
+        if (mChildScale != scale) {
+            mChildScale = scale;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).setScaleX(scale);
+                getChildAt(i).setScaleY(scale);
+            }
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        child.setScaleX(mChildScale);
+        child.setScaleY(mChildScale);
+    }
+
+    @Override
+    protected boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params,
+            boolean preventRequestLayout) {
+        boolean ret = super.addViewInLayout(child, index, params, preventRequestLayout);
+        if (ret) {
+            child.setScaleX(mChildScale);
+            child.setScaleY(mChildScale);
+        }
+        return ret;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         final int count = getChildCount();
@@ -170,4 +201,5 @@
     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 1c3835f..ef2c094 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -13,6 +13,7 @@
  */
 package android.support.v17.leanback.widget;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -21,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.SoundPool;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -43,16 +45,22 @@
 import android.widget.ImageView;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.RelativeLayout;
-import android.support.v17.leanback.R;
 import android.widget.TextView;
 
+import android.support.v17.leanback.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * A search widget containing a search orb and a text entry view.
  *
- * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
+ * <p>
+ * Note: When {@link SpeechRecognitionCallback} is not used, i.e. using {@link SpeechRecognizer},
+ * your application will need to declare android.permission.RECORD_AUDIO in manifest file.
+ * If your application target >= 23 and the device is running >= 23, it needs implement
+ * {@link SearchBarPermissionListener} where requests runtime permission.
+ * </p>
  */
 public class SearchBar extends RelativeLayout {
     private static final String TAG = SearchBar.class.getSimpleName();
@@ -92,6 +100,21 @@
          * @param query The query set in the search bar at the time the IME is being dismissed.
          */
         public void onKeyboardDismiss(String query);
+
+    }
+
+    /**
+     * Interface that handles runtime permissions requests. App sets listener on SearchBar via
+     * {@link #setPermissionListener(SearchBarPermissionListener)}.
+     */
+    public interface SearchBarPermissionListener {
+
+        /**
+         * Method invoked when SearchBar asks for "android.permission.RECORD_AUDIO" runtime
+         * permission.
+         */
+        void requestAudioPermission();
+
     }
 
     private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener =
@@ -130,6 +153,7 @@
     private boolean mRecognizing = false;
     private final Context mContext;
     private AudioManager mAudioManager;
+    private SearchBarPermissionListener mPermissionListener;
 
     public SearchBar(Context context) {
         this(context, null);
@@ -441,9 +465,6 @@
             }
         }
         mSpeechRecognizer = recognizer;
-        if (mSpeechRecognizer != null) {
-            enforceAudioRecordPermission();
-        }
         if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) {
             throw new IllegalStateException("Can't have speech recognizer and request");
         }
@@ -508,6 +529,14 @@
     }
 
     /**
+     * Returns true if is not running Recognizer, false otherwise.
+     * @return True if is not running Recognizer, false otherwise.
+     */
+    public boolean isRecognizing() {
+        return mRecognizing;
+    }
+
+    /**
      * Stops the speech recognition, if already started.
      */
     public void stopRecognition() {
@@ -537,14 +566,18 @@
     }
 
     /**
-     * Starts the voice recognition.
+     * Sets listener that handles runtime permission requests.
+     * @param listener Listener that handles runtime permission requests.
      */
+    public void setPermissionListener(SearchBarPermissionListener listener) {
+        mPermissionListener = listener;
+    }
+
     public void startRecognition() {
         if (DEBUG) Log.v(TAG, String.format("startRecognition (listening: %s, recognizing: %s)",
                 mListening, mRecognizing));
 
         if (mRecognizing) return;
-        mRecognizing = true;
         if (!hasFocus()) {
             requestFocus();
         }
@@ -552,10 +585,22 @@
             mSearchTextEditor.setText("");
             mSearchTextEditor.setHint("");
             mSpeechRecognitionCallback.recognizeSpeech();
+            mRecognizing = true;
             return;
         }
         if (null == mSpeechRecognizer) return;
+        int res = getContext().checkCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO);
+        if (PackageManager.PERMISSION_GRANTED != res) {
+            if (Build.VERSION.SDK_INT >= 23 && mPermissionListener != null) {
+                mPermissionListener.requestAudioPermission();
+                return;
+            } else {
+                throw new IllegalStateException(Manifest.permission.RECORD_AUDIO +
+                        " required for search");
+            }
+        }
 
+        mRecognizing = true;
         // Request audio focus
         int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
                 // Use the music stream.
@@ -725,14 +770,6 @@
         }
     }
 
-    private void enforceAudioRecordPermission() {
-        String permission = "android.permission.RECORD_AUDIO";
-        int res = getContext().checkCallingOrSelfPermission(permission);
-        if (PackageManager.PERMISSION_GRANTED != res) {
-            throw new IllegalStateException("android.permission.RECORD_AUDIO required for search");
-        }
-    }
-
     private void loadSounds(Context context) {
         int[] sounds = {
                 R.raw.lb_voice_failure,
diff --git a/v17/preference-leanback/Android.mk b/v17/preference-leanback/Android.mk
index abee094..c14c4a6 100644
--- a/v17/preference-leanback/Android.mk
+++ b/v17/preference-leanback/Android.mk
@@ -32,6 +32,7 @@
 LOCAL_AAPT_FLAGS := \
         --auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -45,6 +46,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_JAVA_LIBRARIES := android-support-v17-preference-leanback-res \
         android-support-v17-leanback
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -68,6 +70,7 @@
         android-support-v17-leanback \
         android-support-annotations \
         android-support-v17-preference-leanback-res
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v17/preference-leanback/res/layout/leanback_preferences_list.xml b/v17/preference-leanback/res/layout/leanback_preferences_list.xml
index 21e4e4a..7b640d9 100644
--- a/v17/preference-leanback/res/layout/leanback_preferences_list.xml
+++ b/v17/preference-leanback/res/layout/leanback_preferences_list.xml
@@ -20,4 +20,5 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:transitionGroup="true"
     style="?attr/preferenceFragmentListStyle" />
diff --git a/v17/preference-leanback/res/values/styles.xml b/v17/preference-leanback/res/values/styles.xml
index 42d2b5b..fbb1ad0 100644
--- a/v17/preference-leanback/res/values/styles.xml
+++ b/v17/preference-leanback/res/values/styles.xml
@@ -69,6 +69,8 @@
     <style name="PreferenceFragmentList.Leanback">
         <item name="android:paddingStart">0dp</item>
         <item name="android:paddingEnd">0dp</item>
+        <item name="android:paddingBottom">34dp</item>
+        <item name="android:clipToPadding">false</item>
     </style>
 
 </resources>
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java b/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
index a82f543..2273fb6 100644
--- a/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
+++ b/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
@@ -155,6 +155,9 @@
         @Override
         public void onItemClick(ViewHolder viewHolder) {
             final int index = viewHolder.getAdapterPosition();
+            if (index == RecyclerView.NO_POSITION) {
+                return;
+            }
             final CharSequence entry = mEntryValues[index];
             final ListPreference preference = (ListPreference) getPreference();
             if (index >= 0) {
@@ -207,6 +210,9 @@
         @Override
         public void onItemClick(ViewHolder viewHolder) {
             final int index = viewHolder.getAdapterPosition();
+            if (index == RecyclerView.NO_POSITION) {
+                return;
+            }
             final String entry = mEntryValues[index].toString();
             if (mSelections.contains(entry)) {
                 mSelections.remove(entry);
diff --git a/v17/tests/AndroidManifest.xml b/v17/tests/AndroidManifest.xml
index f4dd293..d9d2909 100644
--- a/v17/tests/AndroidManifest.xml
+++ b/v17/tests/AndroidManifest.xml
@@ -33,13 +33,18 @@
         <activity android:name="android.support.v17.leanback.widget.GridActivity"
             android:exported="true" />
 
+        <activity android:name=
+                          "android.support.v17.leanback.app.wizard.GuidedStepAttributesTestActivity"
+                  android:theme="@style/Theme.Leanback.GuidedStep"
+                  android:exported="true" />
+
         <activity android:name="android.support.v17.leanback.app.BrowseFragmentTestActivity"
-            android:theme="@style/Theme.Leanback.Browse"
-            android:exported="true" />
+                  android:theme="@style/Theme.Leanback.Browse"
+                  android:exported="true" />
 
         <activity android:name="android.support.v17.leanback.app.BrowseSupportFragmentTestActivity"
-            android:theme="@style/Theme.Leanback.Browse"
-            android:exported="true" />
+                  android:theme="@style/Theme.Leanback.Browse"
+                  android:exported="true" />
 
         </application>
 
diff --git a/v17/tests/res/values/strings.xml b/v17/tests/res/values/strings.xml
new file mode 100644
index 0000000..111449e
--- /dev/null
+++ b/v17/tests/res/values/strings.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="search">Search</string>
+    <string name="search_description">SearchFragment test</string>
+    <string name="invert_title">Invert enabled actions</string>
+    <string name="revert_description">Changes enabled actions to disabled &amp; vice versa</string>
+    <string name="focusable_test_error_message">An un-focusable action was incorrectly
+        selected for action title: %s</string>
+    <string name="enabled_test_wrong_focus_error_message">The wrong action was focused</string>
+    <string name="enabled_test_wrong_click_error_message">onClickListener for GuidedAction is
+        incorrectly called for a disabled action</string>
+    <string name="enabled_test_wrong_flag_error_message">isEnabled returns incorrect result
+    </string>
+    <string name="checkbox_title">Option</string>
+    <string name="checkbox_desc">Description of Option</string>
+    <string name="radio_actions_info_title">Radio Actions</string>
+    <string name="radio_actions_info_desc">Only one radio action could be
+        selected at a time</string>
+    <string name="checkbox_actions_info_title">CheckBox Actions</string>
+    <string name="checkbox_actions_info_desc">Multiple checkbox actions can be
+        checked at a time</string>
+    <string name="dropdown_action_title">Drop-down Action%d</string>
+    <string name="dropdown_action_desc">This action may or may not have subactions</string>
+    <string name="subaction_title">Subaction%d</string>
+    <string name="subaction_test_wrong_focus_error_message">The wrong action or sub-action
+        was focused</string>
+    <string name="subaction_test_wrong_click_error_message">The wrong action or sub-action
+        was clicked</string>
+    <string name="datepicker_test_wrong_day_value">The date picker day field shows incorrect value
+    </string>
+    <string name="datepicker_test_wrong_month_value">The date picker month field shows incorrect value
+    </string>
+    <string name="datepicker_test_wrong_year_value">The date picker year field shows incorrect value
+    </string>
+    <string name="datepicker_test_transition_error1">The last day for month %d is incorrect</string>
+    <string name="datepicker_test_transition_error2" formatted="false">
+        Day %d is outside the valid range for month %d</string>
+    <string name="datepicker_with_range_title" formatted="false">Date Picker\nMin: %s\nMax: %s</string>
+    <string name="info_action_very_long_title">Super-long title%d: This is the beginning
+        of one type of a very long
+        title. Make sure that you set multilineDescription flag on for these actions.
+        This allows the long title to be displayed properly.</string>
+    <string name="info_action_very_long_desc">Super-long description%d: This is the beginning
+        of one type of a very long
+        description. Make sure that you set multilineDescription flag on for these actions.
+        This allows the long description to be displayed properly.</string>
+    <string name="multiline_enabled_actions_info_title">Long actions with multiline enabled</string>
+    <string name="multiline_disabled_actions_info_title">
+        Long actions with multiline disabled</string>
+</resources>
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
new file mode 100644
index 0000000..40c1e70
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
@@ -0,0 +1,517 @@
+/*
+ * 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.v17.leanback.app.wizard;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.tests.R;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedDatePickerAction;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.picker.DatePicker;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class GuidedDatePickerTest extends
+        ActivityInstrumentationTestCase2<GuidedStepAttributesTestActivity> {
+
+    static final long TRANSITION_LENGTH = 1000;
+    static long VERTICAL_SCROLL_WAIT = 500;
+    static long HORIZONTAL_SCROLL_WAIT = 500;
+    static final long FINAL_WAIT = 3000;
+
+    static final String TAG = "GuidedDatePickerTest";
+
+    private static final int DAY_INDEX = 0;
+    private static final int MONTH_INDEX = 1;
+    private static final int YEAR_INDEX = 2;
+    Instrumentation mInstrumentation;
+    GuidedStepAttributesTestActivity mActivity;
+
+    public GuidedDatePickerTest() {
+        super(GuidedStepAttributesTestActivity.class);
+    }
+
+    private void initActivity(Intent intent) {
+
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void scrollOnField(int field, int[] columnIndices, DatePicker mPickerView,
+                               int SCROLL_DIR) throws Throwable {
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+
+        int mColDayIndex = columnIndices[0];
+        int mColMonthIndex = columnIndices[1];
+        int mColYearIndex = columnIndices[2];
+        int columnIndex = -1;
+        switch (field) {
+            case Calendar.DAY_OF_MONTH:
+                columnIndex = mColDayIndex;
+                break;
+            case Calendar.MONTH:
+                columnIndex = mColMonthIndex;
+                break;
+            case Calendar.YEAR:
+                columnIndex = mColYearIndex;
+        }
+
+
+        LinearLayout columnsLayout = (LinearLayout) mPickerView.getChildAt(0);
+
+        int focusedFieldPos = columnsLayout.indexOfChild(columnsLayout.getFocusedChild());
+        if (focusedFieldPos == -1) {
+            sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+            Thread.sleep(TRANSITION_LENGTH);
+        }
+        focusedFieldPos = columnsLayout.indexOfChild(columnsLayout.getFocusedChild());
+        assertTrue("Date field could not be focused!", (focusedFieldPos != -1));
+
+        // following is to skip the separator fields "/" which are unfocusable but counted as
+        // children of columnsLayout
+        switch (focusedFieldPos) {
+            case 0:
+                focusedFieldPos = 0;
+                break;
+            case 2:
+                focusedFieldPos = 1;
+                break;
+            case 4:
+                focusedFieldPos = 2;
+        }
+
+        // now scroll right or left to the corresponding date field as indicated by the input field
+        int horizontalScrollOffset = columnIndex - focusedFieldPos;
+
+        int horizontalScrollDir = KeyEvent.KEYCODE_DPAD_RIGHT;
+        if (horizontalScrollOffset < 0) {
+            horizontalScrollOffset = -horizontalScrollOffset;
+            horizontalScrollDir = KeyEvent.KEYCODE_DPAD_LEFT;
+        }
+        for(int i = 0; i < horizontalScrollOffset; i++) {
+            sendKeys(horizontalScrollDir);
+            Thread.sleep(HORIZONTAL_SCROLL_WAIT);
+        }
+
+
+        Calendar currentActionCal = Calendar.getInstance();
+        currentActionCal.setTimeInMillis(mPickerView.getDate());
+
+        Calendar minCal = Calendar.getInstance();
+        minCal.setTimeInMillis(mPickerView.getMinDate());
+
+        Calendar maxCal = Calendar.getInstance();
+        maxCal.setTimeInMillis(mPickerView.getMaxDate());
+
+
+        int prevColumnVal = -1;
+        int currentColumnVal = mPickerView.getColumnAt(columnIndex).getCurrentValue();
+        while( currentColumnVal != prevColumnVal ){
+            assertTrue(getActivity().getString(R.string.datepicker_test_wrong_day_value),
+                    mPickerView.getColumnAt(mColDayIndex).getCurrentValue() ==
+                            currentActionCal.get(Calendar.DAY_OF_MONTH)
+            );
+            assertTrue(getActivity().getString(R.string.datepicker_test_wrong_month_value),
+                    mPickerView.getColumnAt(mColMonthIndex).getCurrentValue() ==
+                            currentActionCal.get(Calendar.MONTH)
+            );
+            assertTrue(getActivity().getString(R.string.datepicker_test_wrong_year_value),
+                    mPickerView.getColumnAt(mColYearIndex).getCurrentValue() ==
+                            currentActionCal.get(Calendar.YEAR)
+            );
+
+            int offset = SCROLL_DIR == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : -1;
+            addDate(currentActionCal, field, offset, minCal, maxCal);
+
+            sendKeys(SCROLL_DIR);
+            Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+            prevColumnVal = currentColumnVal;
+            currentColumnVal = mPickerView.getColumnAt(columnIndex).getCurrentValue();
+        }
+    }
+
+    private void addDate(Calendar mCurrentDate, int field, int offset,
+                         Calendar mMinDate, Calendar mMaxDate) {
+        int maxOffset = -1;
+        int actualMinFieldValue, actualMaxFieldValue;
+
+        if ( field == Calendar.YEAR ) {
+            actualMinFieldValue = mMinDate.get(Calendar.YEAR);
+            actualMaxFieldValue = mMaxDate.get(Calendar.YEAR);
+        } else {
+            actualMinFieldValue = mCurrentDate.getActualMinimum(field);
+            actualMaxFieldValue = mCurrentDate.getActualMaximum(field);
+        }
+
+        if ( offset > 0 ) {
+            maxOffset = Math.min(
+                    actualMaxFieldValue - mCurrentDate.get(field), offset);
+            mCurrentDate.add(field, maxOffset);
+            if (mCurrentDate.after(mMaxDate)) {
+                mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+            }
+        } else {
+            maxOffset = Math.max(
+                    actualMinFieldValue - mCurrentDate.get(field), offset);
+            mCurrentDate.add(field, Math.max(offset, maxOffset));
+            if (mCurrentDate.before(mMinDate)) {
+                mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+            }
+        }
+    }
+
+    public void testDifferentMonthLengths() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        final int NUM_DATE_ACTIONS = 1;
+
+        String title = "Date Picker Transition Test";
+        String breadcrumb = "Month Transition Test Demo";
+        String description = "Testing the transition between longer to shorter months";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+
+        Calendar cal = Calendar.getInstance();
+
+        cal.set(Calendar.YEAR, 2016);
+        cal.set(Calendar.MONTH, Calendar.JANUARY);
+        cal.set(Calendar.DAY_OF_MONTH, 30);
+        Date initialDate = cal.getTime();
+
+        GuidedDatePickerAction action = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(0)
+                .title("Date")
+                .date(initialDate.getTime())
+                .datePickerFormat("DMY")
+                .build();
+
+        actionList.add(action);
+
+        GuidedStepAttributesTestFragment.clear();
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+        initActivity(intent);
+
+        DatePicker mPickerView = (DatePicker) mActivity.findViewById(
+                R.id.guidedactions_activator_item);
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+                getGuidedStepTestFragment();
+        traverseMonths(mPickerView, (GuidedDatePickerAction) actionList.get(0));
+        Thread.sleep(FINAL_WAIT);
+    }
+
+    private void traverseMonths(DatePicker mPickerView, GuidedDatePickerAction dateAction)
+            throws Throwable{
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+
+        Calendar currentActionCal = Calendar.getInstance();
+        currentActionCal.setTimeInMillis(dateAction.getDate());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+
+        int prevMonth = -1;
+        int currentMonth = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+        while (currentMonth != prevMonth) {
+            int prevDayOfMonth = -1;
+            int currentDayOfMonth = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+            // scroll down the days till reaching the last day of month
+            while (currentDayOfMonth != prevDayOfMonth) {
+                sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+                Thread.sleep(VERTICAL_SCROLL_WAIT);
+                prevDayOfMonth = currentDayOfMonth;
+                currentDayOfMonth = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+            }
+            int oldDayValue = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+            int oldMonthValue = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+            // increment the month
+            sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+            Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+            Thread.sleep(TRANSITION_LENGTH);
+
+            int newDayValue = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+            int newMonthValue = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+            verifyMonthTransition(currentActionCal,
+                    oldDayValue, oldMonthValue, newDayValue, newMonthValue);
+
+            sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+            Thread.sleep(TRANSITION_LENGTH);
+            prevMonth = currentMonth;
+            currentMonth = newMonthValue;
+        }
+
+    }
+
+    private void verifyMonthTransition(Calendar currentCal, int oldDayValue, int oldMonthValue,
+                                       int newDayValue, int newMonthValue) {
+
+        if (oldMonthValue == newMonthValue)
+            return;
+
+        currentCal.set(Calendar.DAY_OF_MONTH, 1);
+        currentCal.set(Calendar.MONTH, oldMonthValue);
+        int expectedOldDayValue = currentCal.getActualMaximum(Calendar.DAY_OF_MONTH);
+        currentCal.set(Calendar.MONTH, newMonthValue);
+        int numDaysInNewMonth = currentCal.getActualMaximum(Calendar.DAY_OF_MONTH);
+        int expectedNewDayValue = (expectedOldDayValue <= numDaysInNewMonth) ?
+                expectedOldDayValue : numDaysInNewMonth;
+
+        assertTrue(getActivity().getString(
+                R.string.datepicker_test_transition_error1, oldMonthValue),
+                oldDayValue == expectedOldDayValue
+        );
+        assertTrue(getActivity().getString(
+                R.string.datepicker_test_transition_error2, newDayValue, newMonthValue),
+                newDayValue == expectedNewDayValue
+        );
+    }
+
+    public void testDateRanges() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
+
+        Calendar currCal = Calendar.getInstance();
+        currCal.set(Calendar.YEAR, 2016);
+        currCal.set(Calendar.MONTH, Calendar.JULY);
+        currCal.set(Calendar.DAY_OF_MONTH, 15);
+
+        Calendar minCal = Calendar.getInstance();
+        minCal.set(Calendar.YEAR, 2014);
+        minCal.set(Calendar.MONTH, Calendar.OCTOBER);
+        minCal.set(Calendar.DAY_OF_MONTH, 20);
+
+        Calendar maxCal = Calendar.getInstance();
+        maxCal.set(Calendar.YEAR, 2018);
+        maxCal.set(Calendar.MONTH, Calendar.FEBRUARY);
+        maxCal.set(Calendar.DAY_OF_MONTH, 10);
+
+        String title = "Date Picker Range Test";
+        String breadcrumb = "Date Picker Range Test Demo";
+        String description = "";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+
+        // testing different date formats and the correctness of range changes as we scroll
+        GuidedDatePickerAction dateAction1 = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(0)
+                .title(res.getString(R.string.datepicker_with_range_title,
+                        dateFormat.format(minCal.getTime()),
+                        dateFormat.format(maxCal.getTime())))
+                .multilineDescription(true)
+                .date(currCal.getTimeInMillis())
+                .datePickerFormat("MDY")
+                .minDate(minCal.getTimeInMillis())
+                .maxDate(maxCal.getTimeInMillis())
+                .build();
+
+        GuidedDatePickerAction dateAction2 = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(1)
+                .title(res.getString(R.string.datepicker_with_range_title,
+                        dateFormat.format(minCal.getTimeInMillis()),
+                        dateFormat.format(maxCal.getTimeInMillis())))
+                .multilineDescription(true)
+                .date(currCal.getTimeInMillis())
+                .datePickerFormat("DMY")
+                .minDate(minCal.getTimeInMillis())
+                .maxDate(maxCal.getTimeInMillis())
+                .build();
+
+        // testing date ranges when Year is equal
+        minCal.set(Calendar.YEAR, maxCal.get(Calendar.YEAR));
+        int minMonth = Math.min(minCal.get(Calendar.MONTH), maxCal.get(Calendar.MONTH));
+        int maxMonth = Math.max(minCal.get(Calendar.MONTH), maxCal.get(Calendar.MONTH));
+        minCal.set(Calendar.MONTH, minMonth);
+        maxCal.set(Calendar.MONTH, maxMonth);
+
+        GuidedDatePickerAction dateAction3 = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(2)
+                .title(res.getString(R.string.datepicker_with_range_title,
+                        dateFormat.format(minCal.getTimeInMillis()),
+                        dateFormat.format(maxCal.getTimeInMillis())))
+                .multilineDescription(true)
+                .date(currCal.getTimeInMillis())
+                .datePickerFormat("DMY")
+                .minDate(minCal.getTimeInMillis())
+                .maxDate(maxCal.getTimeInMillis())
+                .build();
+
+
+        // testing date ranges when both Month and Year are equal
+        minCal.set(Calendar.MONTH, maxCal.get(Calendar.MONTH));
+        int minDay = Math.min(minCal.get(Calendar.DAY_OF_MONTH), maxCal.get(Calendar.DAY_OF_MONTH));
+        int maxDay = Math.max(minCal.get(Calendar.DAY_OF_MONTH), maxCal.get(Calendar.DAY_OF_MONTH));
+        minCal.set(Calendar.DAY_OF_MONTH, minDay);
+        maxCal.set(Calendar.DAY_OF_MONTH, maxDay);
+
+        GuidedDatePickerAction dateAction4 = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(3)
+                .title(res.getString(R.string.datepicker_with_range_title,
+                        dateFormat.format(minCal.getTimeInMillis()),
+                        dateFormat.format(maxCal.getTimeInMillis())))
+                .multilineDescription(true)
+                .date(currCal.getTimeInMillis())
+                .datePickerFormat("DMY")
+                .minDate(minCal.getTimeInMillis())
+                .maxDate(maxCal.getTimeInMillis())
+                .build();
+
+
+        // testing date ranges when all fields are equal
+        minCal.set(Calendar.DAY_OF_MONTH, maxCal.get(Calendar.DAY_OF_MONTH));
+
+        GuidedDatePickerAction dateAction5 = new GuidedDatePickerAction.Builder(
+                mInstrumentation.getContext())
+                .id(4)
+                .title(res.getString(R.string.datepicker_with_range_title,
+                        dateFormat.format(minCal.getTimeInMillis()),
+                        dateFormat.format(maxCal.getTimeInMillis())))
+                .multilineDescription(true)
+                .date(currCal.getTimeInMillis())
+                .datePickerFormat("DMY")
+                .minDate(minCal.getTimeInMillis())
+                .maxDate(maxCal.getTimeInMillis())
+                .build();
+
+        actionList.add(dateAction1);
+        actionList.add(dateAction2);
+        actionList.add(dateAction3);
+        actionList.add(dateAction4);
+        actionList.add(dateAction5);
+
+        GuidedStepAttributesTestFragment.clear();
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+        initActivity(intent);
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+                getGuidedStepTestFragment();
+
+        scrollToMinAndMaxDates(new int[] {1, 0, 2}, dateAction1);
+        scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction2);
+        scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction3);
+        scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction4);
+        scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction5);
+
+        Thread.sleep(FINAL_WAIT);
+    }
+
+    private void scrollToMinAndMaxDates(int[] columnIndices, GuidedDatePickerAction dateAction)
+            throws Throwable{
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+
+        VerticalGridView guidedActionsList = (VerticalGridView)
+                mActivity.findViewById(R.id.guidedactions_list);
+
+        int currSelectedAction = mFragment.getSelectedActionPosition();
+        // scroll up/down to the requested action
+        long verticalScrollOffset = dateAction.getId() - currSelectedAction;
+
+        int verticalScrollDir = KeyEvent.KEYCODE_DPAD_DOWN;
+        if (verticalScrollOffset < 0) {
+            verticalScrollOffset= -verticalScrollOffset;
+            verticalScrollDir = KeyEvent.KEYCODE_DPAD_UP;
+        }
+        for(int i = 0; i < verticalScrollOffset; i++) {
+            sendKeys(verticalScrollDir);
+            Thread.sleep(TRANSITION_LENGTH);
+        }
+
+        assertTrue("The wrong action was selected!", mFragment.getSelectedActionPosition() ==
+                dateAction.getId());
+        DatePicker mPickerView = (DatePicker) mFragment.getActionItemView((int) dateAction.getId())
+                .findViewById(R.id.guidedactions_activator_item);
+
+        Calendar currentActionCal = Calendar.getInstance();
+        currentActionCal.setTimeInMillis(dateAction.getDate());
+
+
+        // scrolling to the minimum date
+
+        scrollOnField(Calendar.YEAR, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+        dateAction.setDate(mPickerView.getDate());
+
+        scrollOnField(Calendar.MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+        dateAction.setDate(mPickerView.getDate());
+
+        scrollOnField(Calendar.DAY_OF_MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+        dateAction.setDate(mPickerView.getDate());
+
+        Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+        // now scrolling to the maximum date
+
+        scrollOnField(Calendar.YEAR, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+        dateAction.setDate(mPickerView.getDate());
+
+        scrollOnField(Calendar.MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+        dateAction.setDate(mPickerView.getDate());
+
+        scrollOnField(Calendar.DAY_OF_MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+        dateAction.setDate(mPickerView.getDate());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+    }
+
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
new file mode 100644
index 0000000..d702515
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
@@ -0,0 +1,623 @@
+/*
+ * 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.v17.leanback.app.wizard;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.tests.R;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class GuidedStepAttributesTest extends
+        ActivityInstrumentationTestCase2<GuidedStepAttributesTestActivity>
+{
+    static final long TRANSITION_LENGTH = 1000;
+
+    static final String TAG = "GuidedStepAttributesTest";
+
+    Instrumentation mInstrumentation;
+    GuidedStepAttributesTestActivity mActivity;
+
+    public GuidedStepAttributesTest() {
+        super(GuidedStepAttributesTestActivity.class);
+    }
+
+    private void initActivity(Intent intent) {
+
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void testFocusDisabledOnActions() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        final int NUM_SEARCH_ACTIONS = 10;
+        final List<Integer> ACTIONS_WITH_DISABLED_FOCUS = new ArrayList<>(
+                Arrays.asList(1, 3, 4, 5, 8));
+        final int ACTION_ID_SEARCH = 1;
+        List<Integer> EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT = new ArrayList<>();
+
+        // we will traverse actions from top to bottom and then back to the top
+        for(int i = 0; i < NUM_SEARCH_ACTIONS; i++) {
+            if (!ACTIONS_WITH_DISABLED_FOCUS.contains(i))
+                EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.add(i);
+        }
+        for(int i = EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.size(); i-- != 0;) {
+            EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.add(EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(i));
+        }
+
+
+        String title = "Guided Actions Focusable Test";
+        String breadcrumb = "Focusable Test Demo";
+        String description = "";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+        for (int i = 0; i < NUM_SEARCH_ACTIONS; i++ ) {
+            actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                    .id(ACTION_ID_SEARCH)
+                    .title(res.getString(R.string.search) + "" + i)
+                    .description(res.getString(R.string.search_description) + i)
+                    .build()
+            );
+        }
+        for(int action_id : ACTIONS_WITH_DISABLED_FOCUS )
+            actionList.get(action_id).setFocusable(false);
+
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+        initActivity(intent);
+
+        int lastSelectedActionId = -1;
+        int selectIndex = 0;
+        GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.getGuidedStepTestFragment();
+        int prevSelectedActionPosition = -1;
+        int nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+        while ( nextSelectedActionPosition != prevSelectedActionPosition ) {
+            lastSelectedActionId = mFragment.getSelectedActionPosition();
+            assertTrue(res.getString(R.string.focusable_test_error_message,
+                    actionList.get(lastSelectedActionId).getTitle()),
+                    lastSelectedActionId == EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(selectIndex));
+            selectIndex++;
+            sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+            prevSelectedActionPosition = nextSelectedActionPosition;
+            nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+            Thread.sleep(TRANSITION_LENGTH);
+        }
+
+        prevSelectedActionPosition = -1;
+        while ( nextSelectedActionPosition != prevSelectedActionPosition ) {
+            lastSelectedActionId = mFragment.getSelectedActionPosition();
+            assertTrue(res.getString(R.string.focusable_test_error_message,
+                    actionList.get(lastSelectedActionId).getTitle()),
+                    lastSelectedActionId == EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(selectIndex));
+            selectIndex++;
+            sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+            prevSelectedActionPosition = nextSelectedActionPosition;
+            nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+            Thread.sleep(TRANSITION_LENGTH);
+        }
+
+    }
+
+    // Note: do not remove final or sRevertCallback gets null from 2nd test on!
+     static final GuidedStepAttributesTestFragment.Callback sRevertCallback = new
+            GuidedStepAttributesTestFragment.Callback() {
+        @Override
+        public void onActionClicked(GuidedStepFragment fragment, long id) {
+            List<GuidedAction> allActions = fragment.getActions();
+            for(int i = 1; i < allActions.size(); i++) {
+                GuidedAction action = allActions.get(i);
+                action.setEnabled(!action.isEnabled());
+                fragment.notifyActionChanged(fragment.findActionPositionById(action.getId()));
+            }
+        }
+    };
+
+    public void testDisabledActions() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        final int NUM_SEARCH_ACTIONS = 10;
+        final List<Integer> DISABLED_ACTIONS = new ArrayList<>(
+                Arrays.asList(1, 3, 5, 7));
+        final int ACTION_ID_REVERT_BUTTON = 0;
+        final int ACTION_ID_SEARCH_BEGIN = ACTION_ID_REVERT_BUTTON + 1;
+        int ACTION_ID_SEARCH_END = ACTION_ID_SEARCH_BEGIN;
+
+        // sequence of clicked actions simulated in the test
+        List<Integer> CLICK_SEQUENCE = new ArrayList<>();
+
+        // Expected Clicked sequence can be different from focused ones since some of the actions
+        // are disabled hence not clickable
+        List<Integer> EXPECTED_FOCUSED_SEQUENCE = new ArrayList<>();
+        List<Integer> EXPECTED_CLICKED_SEQUENCE = new ArrayList<>();
+        // Expected actions state according to list of DISABLED_ACTIONS: false for disabled actions
+        List<Boolean> EXPECTED_ACTIONS_STATE = new ArrayList<>(
+                Arrays.asList(new Boolean[NUM_SEARCH_ACTIONS])
+        );
+        Collections.fill(EXPECTED_ACTIONS_STATE, Boolean.TRUE);
+
+        for(int i = 0; i < NUM_SEARCH_ACTIONS; i++) {
+            CLICK_SEQUENCE.add(i + 1);
+        }
+        for(int clickedActionId : CLICK_SEQUENCE) {
+            EXPECTED_FOCUSED_SEQUENCE.add(clickedActionId);
+            if (!DISABLED_ACTIONS.contains(clickedActionId - 1))
+                EXPECTED_CLICKED_SEQUENCE.add(clickedActionId);
+            else
+                EXPECTED_CLICKED_SEQUENCE.add(-1);
+        }
+
+        String title = "Guided Actions Enabled Test";
+        String breadcrumb = "Enabled Test Demo";
+        String description = "";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+        actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                .id(ACTION_ID_REVERT_BUTTON)
+                .title(res.getString(R.string.invert_title))
+                .description(res.getString(R.string.revert_description))
+                .build()
+        );
+
+        for (int i = 0; i < NUM_SEARCH_ACTIONS; i++ ) {
+            actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                    .id(ACTION_ID_SEARCH_END++)
+                    .title(res.getString(R.string.search) + "" + i)
+                    .description(res.getString(R.string.search_description) + i)
+                    .build()
+            );
+        }
+        for(int action_id : DISABLED_ACTIONS ) {
+            if ( action_id >= 0 && action_id < NUM_SEARCH_ACTIONS ) {
+                actionList.get(action_id + 1).setEnabled(false);
+                EXPECTED_ACTIONS_STATE.set(action_id, Boolean.FALSE);
+            }
+        }
+
+        GuidedStepAttributesTestFragment.clear();
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+        GuidedStepAttributesTestFragment.setActionClickCallback(ACTION_ID_REVERT_BUTTON,
+                sRevertCallback);
+
+        initActivity(intent);
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+
+        examineEnabledAndDisabledActions(actionList, CLICK_SEQUENCE, EXPECTED_FOCUSED_SEQUENCE,
+                EXPECTED_CLICKED_SEQUENCE);
+        // now toggling all enabled/disabled actions to disabled/enabled and running the test again
+        Log.d(TAG, "Toggling actions...");
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mFragment.setSelectedActionPosition(0);
+            }
+        });
+        Thread.sleep(TRANSITION_LENGTH);
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        for(int i = 0; i < EXPECTED_CLICKED_SEQUENCE.size(); i++) {
+            if (EXPECTED_CLICKED_SEQUENCE.get(i) == -1)
+                EXPECTED_CLICKED_SEQUENCE.set(i, CLICK_SEQUENCE.get(i));
+            else
+                EXPECTED_CLICKED_SEQUENCE.set(i, -1);
+        }
+
+        examineEnabledAndDisabledActions(actionList, CLICK_SEQUENCE, EXPECTED_FOCUSED_SEQUENCE,
+                EXPECTED_CLICKED_SEQUENCE);
+
+    }
+
+    private void examineEnabledAndDisabledActions(
+            List<GuidedAction> actionList, List<Integer> CLICK_SEQUENCE,
+                                List<Integer> EXPECTED_FOCUSED_SEQUENCE,
+                                List<Integer> EXPECTED_CLICKED_SEQUENCE)
+            throws Throwable {
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+
+        for(int i = 0; i < CLICK_SEQUENCE.size(); i++) {
+            GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID =
+                    GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID = -1;
+            final int id = CLICK_SEQUENCE.get(i);
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mFragment.setSelectedActionPosition(id);
+                }
+            });
+            Thread.sleep(TRANSITION_LENGTH);
+
+            sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+            Thread.sleep(TRANSITION_LENGTH);
+
+            assertTrue(mInstrumentation.getContext().getResources().getString(
+                    R.string.enabled_test_wrong_focus_error_message),
+                    GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                            EXPECTED_FOCUSED_SEQUENCE.get(i)
+            );
+            assertTrue(mInstrumentation.getContext().getResources().getString(
+                    R.string.enabled_test_wrong_click_error_message),
+                    GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+                            EXPECTED_CLICKED_SEQUENCE.get(i)
+            );
+            assertTrue(mInstrumentation.getContext().getResources().getString(
+                    R.string.enabled_test_wrong_flag_error_message),
+                    (GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID == -1) ?
+                            !actionList.get(id).isEnabled() :
+                            actionList.get(id).isEnabled()
+            );
+        }
+    }
+
+    public void testCheckedActions() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        final int NUM_RADIO_ACTIONS = 3;
+        final int NUM_CHECK_BOX_ACTIONS = 3;
+        final int INITIALLY_CHECKED_RADIO_ACTION = 0;
+        final List<Integer> INITIALLY_CHECKED_CHECKBOX_ACTIONS = new ArrayList<>(
+                Arrays.asList(1, 2)
+        );
+
+        List<Integer> CLICK_SEQUENCE = new ArrayList<>();
+        for(int i = 0; i < NUM_RADIO_ACTIONS + NUM_CHECK_BOX_ACTIONS; i++) {
+            CLICK_SEQUENCE.add(i);
+        }
+
+        List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK = new ArrayList<>(
+                Arrays.asList(new Boolean[CLICK_SEQUENCE.size()])
+        );
+        Collections.fill(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK, Boolean.FALSE);
+
+        // initial state of actions before any clicks happen
+        EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(INITIALLY_CHECKED_RADIO_ACTION, true);
+        for(int checkedCheckBox : INITIALLY_CHECKED_CHECKBOX_ACTIONS) {
+            EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(NUM_RADIO_ACTIONS + checkedCheckBox, true);
+        }
+
+        String title = "Guided Actions Checked Test";
+        String breadcrumb = "Checked Test Demo";
+        String description = "";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+        actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                .title(res.getString(R.string.radio_actions_info_title))
+                .description(res.getString(R.string.radio_actions_info_desc))
+                .infoOnly(true)
+                .enabled(true)
+                .focusable(false)
+                .build()
+        );
+
+        int firstRadioActionIndex = actionList.size();
+        for(int i = 0; i < NUM_RADIO_ACTIONS; i++) {
+            actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                    .title(res.getString(R.string.checkbox_title) + i)
+                    .description(res.getString(R.string.checkbox_desc) + i)
+                    .checkSetId(GuidedAction.DEFAULT_CHECK_SET_ID)
+                    .build()
+            );
+            if (i == INITIALLY_CHECKED_RADIO_ACTION)
+                actionList.get(firstRadioActionIndex + i).setChecked(true);
+        }
+
+        actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                .title(res.getString(R.string.checkbox_actions_info_title))
+                .description(res.getString(R.string.checkbox_actions_info_desc))
+                .infoOnly(true)
+                .enabled(true)
+                .focusable(false)
+                .build()
+        );
+        int firstCheckBoxActionIndex = actionList.size();
+        for(int i = 0; i < NUM_CHECK_BOX_ACTIONS; i++) {
+            actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                    .title(res.getString(R.string.checkbox_title) + i)
+                    .description(res.getString(R.string.checkbox_desc) + i)
+                    .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID)
+                    .build()
+            );
+        }
+        for(int i = 0; i < INITIALLY_CHECKED_CHECKBOX_ACTIONS.size(); i++ ) {
+            actionList.get(firstCheckBoxActionIndex + INITIALLY_CHECKED_CHECKBOX_ACTIONS.get(i))
+                    .setChecked(true);
+        }
+
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+        initActivity(intent);
+
+        examineCheckedAndUncheckedActions(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+                NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS);
+    }
+
+    private void updateExpectedActionsStateAfterClick(
+            List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK, int NUM_RADIO_ACTIONS,
+            int NUM_CHECK_BOX_ACTIONS, int clickedActionIndex) {
+
+        if (clickedActionIndex < NUM_RADIO_ACTIONS) {
+            for(int i = 0; i < NUM_RADIO_ACTIONS; i++)
+                EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(i, false);
+            EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(clickedActionIndex, true);
+        }
+        else if (clickedActionIndex < NUM_RADIO_ACTIONS + NUM_CHECK_BOX_ACTIONS) {
+            EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(clickedActionIndex,
+                    !EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.get(clickedActionIndex));
+        }
+    }
+
+    private void verifyIfActionsStateIsCorrect(List<GuidedAction> actionList,
+            List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK) {
+
+        int actionIndex = 0;
+        for(GuidedAction checkAction : actionList) {
+            if (checkAction.infoOnly())
+                continue;
+            assertTrue("Action " + actionIndex + " is " +
+                            (!checkAction.isChecked() ? "un-" : "") +
+                    "checked while it shouldn't be!",
+                    checkAction.isChecked() ==
+                            EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.get(actionIndex));
+            actionIndex++;
+        }
+    }
+
+    private void examineCheckedAndUncheckedActions(List<GuidedAction> actionList,
+                                List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+                                int NUM_RADIO_ACTIONS,
+                                int NUM_CHECK_BOX_ACTIONS) throws Throwable {
+
+        final GuidedStepFragment guidedStepCheckedFragment = (GuidedStepFragment)
+                mActivity.getGuidedStepTestFragment();
+        final int firstRadioActionIndex = 1;
+        final int firstCheckBoxActionIndex = firstRadioActionIndex + NUM_RADIO_ACTIONS + 1;
+        for(int actionId = 0; actionId < NUM_RADIO_ACTIONS; actionId++) {
+            final int id = actionId;
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    guidedStepCheckedFragment
+                            .setSelectedActionPosition(firstRadioActionIndex + id);
+                }
+            });
+            Thread.sleep(TRANSITION_LENGTH);
+
+            sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+            Thread.sleep(TRANSITION_LENGTH);
+            updateExpectedActionsStateAfterClick(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+                    NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS, actionId);
+            verifyIfActionsStateIsCorrect(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK);
+        }
+
+        for(int actionId = 0; actionId < NUM_CHECK_BOX_ACTIONS; actionId++) {
+            final int id = actionId;
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    guidedStepCheckedFragment
+                            .setSelectedActionPosition(firstCheckBoxActionIndex + id);
+                }
+            });
+            Thread.sleep(TRANSITION_LENGTH);
+
+            sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+            Thread.sleep(TRANSITION_LENGTH);
+            updateExpectedActionsStateAfterClick(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+                    NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS, NUM_RADIO_ACTIONS + actionId);
+            verifyIfActionsStateIsCorrect(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK);
+        }
+    }
+
+    public void testSubActions() throws Throwable {
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(),
+                GuidedStepAttributesTestActivity.class);
+        Resources res = mInstrumentation.getContext().getResources();
+
+        String TAG = "testSubActions";
+        final int NUM_REGULAR_ACTIONS = 4;
+        final int[] NUM_SUBACTIONS_PER_ACTION = {2, 1, 0, 3};
+        final int[] REGULAR_ACTIONS_INDEX =  new int[NUM_REGULAR_ACTIONS];
+        final int[] BEGIN_SUBACTION_INDEX_PER_ACTION = new int[NUM_REGULAR_ACTIONS];
+        final int[] END_SUBACTION_INDEX_PER_ACTION = new int[NUM_REGULAR_ACTIONS];
+        // Actions and Subactions are assigned unique sequential IDs
+        int lastIndex = 0;
+        for(int i = 0; i < NUM_REGULAR_ACTIONS; i++) {
+            REGULAR_ACTIONS_INDEX[i] = lastIndex;
+            lastIndex++;
+            BEGIN_SUBACTION_INDEX_PER_ACTION[i] = lastIndex;
+            END_SUBACTION_INDEX_PER_ACTION[i] = (lastIndex += NUM_SUBACTIONS_PER_ACTION[i]);
+        }
+
+        // Sample click sequence for the main action list (not subactions)
+        List<Integer> ACTION_CLICK_SEQUENCE = new ArrayList<>(Arrays.asList(
+                3, 2, 1, 0
+        ));
+        List<Integer> EXPECTED_FOCUSED_SEQUENCE = new ArrayList<>();
+        List<Integer> EXPECTED_CLICKED_SEQUENCE = new ArrayList<>();
+
+        for(int clickedActionId : ACTION_CLICK_SEQUENCE) {
+            EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+            if (NUM_SUBACTIONS_PER_ACTION[clickedActionId] > 0) {
+                for (int i = BEGIN_SUBACTION_INDEX_PER_ACTION[clickedActionId]; i <
+                        END_SUBACTION_INDEX_PER_ACTION[clickedActionId]; i++) {
+                    EXPECTED_CLICKED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+                    for (int j = BEGIN_SUBACTION_INDEX_PER_ACTION[clickedActionId]; j <= i; j++) {
+                        EXPECTED_FOCUSED_SEQUENCE.add(j);
+                    }
+                    EXPECTED_CLICKED_SEQUENCE.add(i);
+                    EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+                }
+            } else {
+                EXPECTED_CLICKED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+                EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+            }
+        }
+
+        String title = "Guided SubActions Test";
+        String breadcrumb = "SubActions Test Demo";
+        String description = "";
+        GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+                breadcrumb, null);
+
+        List<GuidedAction> actionList = new ArrayList<>();
+
+        lastIndex = 0;
+        for (int i = 0; i < NUM_REGULAR_ACTIONS; i++ ) {
+            GuidedAction action = new GuidedAction.Builder(mInstrumentation.getContext())
+                    .id(lastIndex++)
+                    .title(res.getString(R.string.dropdown_action_title, i))
+                    .description(res.getString(R.string.dropdown_action_desc, i))
+                    .build();
+            if (NUM_SUBACTIONS_PER_ACTION[i] > 0) {
+                List<GuidedAction> subActions = new ArrayList<>();
+                action.setSubActions(subActions);
+                for(int j = 0; j < NUM_SUBACTIONS_PER_ACTION[i]; j++) {
+                    subActions.add(new GuidedAction.Builder(mInstrumentation.getContext())
+                            .id(lastIndex++)
+                            .title(res.getString(R.string.subaction_title, j))
+                            .description("")
+                            .build()
+                    );
+                }
+            }
+            actionList.add(action);
+        }
+
+        GuidedStepAttributesTestFragment.clear();
+        GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+        GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+        initActivity(intent);
+
+        final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+                getGuidedStepTestFragment();
+
+        int focusStep = 0, clickStep = 0;
+        for(int i = 0; i < ACTION_CLICK_SEQUENCE.size(); i++) {
+            GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID =
+                    GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID = -1;
+            final int id = ACTION_CLICK_SEQUENCE.get(i);
+            final GuidedAction selectedAction = actionList.get(id);
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mFragment.setSelectedActionPosition(id);
+                }
+            });
+            Thread.sleep(TRANSITION_LENGTH);
+
+            assertTrue(mInstrumentation.getContext().getResources().getString(
+                    R.string.subaction_test_wrong_focus_error_message),
+                    GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                            EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+            );
+
+            if (selectedAction.hasSubActions()) {
+                // Following for loop clicks on a specific action and scrolls & clicks through
+                // all its subactions
+                for (int j = 0; j < selectedAction.getSubActions().size(); j++) {
+                    sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+                    Thread.sleep(TRANSITION_LENGTH);
+                    assertTrue(mInstrumentation.getContext().getResources().getString(
+                            R.string.subaction_test_wrong_focus_error_message),
+                            GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                                    EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+                    );
+                    assertTrue(mInstrumentation.getContext().getResources().getString(
+                            R.string.subaction_test_wrong_click_error_message),
+                            GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+                                    EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+                    );
+                    for (int k = 0; k < j; k++) {
+                        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+                        Thread.sleep(TRANSITION_LENGTH);
+                        assertTrue(mInstrumentation.getContext().getResources().getString(
+                                R.string.subaction_test_wrong_focus_error_message),
+                                GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                                        EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+                        );
+                    }
+                    sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+                    Thread.sleep(TRANSITION_LENGTH);
+
+                    assertTrue(mInstrumentation.getContext().getResources().getString(
+                            R.string.subaction_test_wrong_focus_error_message),
+                            GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                                    EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+                    );
+                    assertTrue(mInstrumentation.getContext().getResources().getString(
+                            R.string.subaction_test_wrong_click_error_message),
+                            GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+                                    EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+                    );
+                }
+            } else {
+                sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(TRANSITION_LENGTH);
+                assertTrue(mInstrumentation.getContext().getResources().getString(
+                        R.string.subaction_test_wrong_focus_error_message),
+                        GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+                                EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+                );
+                assertTrue(mInstrumentation.getContext().getResources().getString(
+                        R.string.subaction_test_wrong_click_error_message),
+                        GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+                                EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+                );
+            }
+        }
+
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
new file mode 100644
index 0000000..a0433cc
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.v17.leanback.app.wizard;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.app.GuidedStepFragment;
+
+public class GuidedStepAttributesTestActivity extends Activity {
+
+    private GuidedStepAttributesTestFragment mGuidedStepAttributesTestFragment;
+
+    public static String EXTRA_GUIDANCE = "guidance";
+    public static String EXTRA_ACTION_LIST = "actionList";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+
+        mGuidedStepAttributesTestFragment = new GuidedStepAttributesTestFragment();
+        GuidedStepFragment.addAsRoot(this, mGuidedStepAttributesTestFragment, android.R.id.content);
+    }
+    public Fragment getGuidedStepTestFragment() {
+        return getFragmentManager().findFragmentById(android.R.id.content);
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
new file mode 100644
index 0000000..3819dac
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
@@ -0,0 +1,100 @@
+/*
+ * 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.v17.leanback.app.wizard;
+
+import android.os.Bundle;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class GuidedStepAttributesTestFragment extends GuidedStepFragment {
+
+    private static String TAG = "GuidedStepAttributesTestFragment";
+    static class Callback {
+        public void onActionClicked(GuidedStepFragment fragment, long id) {
+        }
+    }
+
+    static HashMap<Long, Callback> sCallbacks = new HashMap();
+    public static GuidanceStylist.Guidance GUIDANCE = null;
+    public static List<GuidedAction> ACTION_LIST = null;
+    public static long LAST_CLICKED_ACTION_ID = -1;
+    public static long LAST_SELECTED_ACTION_ID = -1;
+
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        if (GUIDANCE == null ) {
+            return new GuidanceStylist.Guidance("", "", "", null);
+        }
+        return GUIDANCE;
+    }
+
+    @Override
+    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        if (ACTION_LIST == null)
+            return;
+        actions.addAll(ACTION_LIST);
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        super.onGuidedActionFocused(action);
+        Callback callback = sCallbacks.get(action.getId());
+        if (callback != null) {
+            callback.onActionClicked(this, action.getId());
+        } else {
+            LAST_CLICKED_ACTION_ID = action.getId();
+        }
+    }
+
+    @Override
+    public void onGuidedActionFocused(GuidedAction action) {
+        super.onGuidedActionFocused(action);
+        LAST_SELECTED_ACTION_ID = action.getId();
+    }
+
+    @Override
+    public boolean onSubGuidedActionClicked(GuidedAction action) {
+        super.onSubGuidedActionClicked(action);
+        LAST_CLICKED_ACTION_ID = action.getId();
+        return true;
+    }
+
+    @Override
+    public long onGuidedActionEditedAndProceed(GuidedAction action) {
+
+        Callback callback = sCallbacks.get(action.getId());
+        if (callback != null) {
+            callback.onActionClicked(this, action.getId());
+        } else {
+            super.onGuidedActionEditedAndProceed(action);
+        }
+        return GuidedAction.ACTION_ID_CURRENT;
+    }
+
+    public static void setActionClickCallback(long id, Callback callback) {
+        sCallbacks.put(id, callback);
+    }
+
+    public static void clear() {
+        LAST_CLICKED_ACTION_ID = -1;
+        LAST_SELECTED_ACTION_ID = -1;
+        sCallbacks.clear();
+    }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java b/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java
new file mode 100644
index 0000000..a2ca45c
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for {@link PagingIndicator}.
+ * @hide
+ */
+@SmallTest
+public class PagingIndicatorTest extends AndroidTestCase {
+    private PagingIndicator mIndicator;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIndicator = new PagingIndicator(getContext());
+    }
+
+    public void testDotPosition() {
+        mIndicator.setPageCount(3);
+        assertDotPosition();
+        mIndicator.setPageCount(6);
+        assertDotPosition();
+        mIndicator.setPageCount(9);
+        assertDotPosition();
+    }
+
+    private void assertDotPosition() {
+        assertSymmetry();
+        assertDistance();
+    }
+
+    private void assertSymmetry() {
+        int pageCount = mIndicator.getPageCount();
+        int mid = pageCount / 2;
+        int[] selectedX = mIndicator.getDotSelectedX();
+        int sum = selectedX[0] + selectedX[pageCount - 1];
+        for (int i = 1; i <= mid; ++i) {
+            assertEquals("Selected dots are not symmetric", sum,
+                    selectedX[i] + selectedX[pageCount - i - 1]);
+        }
+        int[] leftX = mIndicator.getDotSelectedLeftX();
+        int[] rightX = mIndicator.getDotSelectedRightX();
+        sum = leftX[0] + rightX[pageCount - 1];
+        for (int i = 1; i < pageCount - 1; ++i) {
+            assertEquals("Deselected dots are not symmetric", sum,
+                    leftX[i] + rightX[pageCount - i - 1]);
+        }
+    }
+
+    private void assertDistance() {
+        int pageCount = mIndicator.getPageCount();
+        int[] selectedX = mIndicator.getDotSelectedX();
+        int[] leftX = mIndicator.getDotSelectedLeftX();
+        int[] rightX = mIndicator.getDotSelectedRightX();
+        int distance = selectedX[1] - selectedX[0];
+        for (int i = 2; i < pageCount; ++i) {
+            assertEquals("Gaps between selected dots are not even", distance,
+                    selectedX[i] - selectedX[i - 1]);
+        }
+        distance = leftX[1] - leftX[0];
+        for (int i = 2; i < pageCount - 1; ++i) {
+            assertEquals("Gaps between left dots are not even", distance,
+                    leftX[i] - leftX[i - 1]);
+        }
+        distance = rightX[2] - rightX[1];
+        for (int i = 3; i < pageCount; ++i) {
+            assertEquals("Gaps between right dots are not even", distance,
+                    rightX[i] - rightX[i - 1]);
+        }
+    }
+}
diff --git a/v4/Android.mk b/v4/Android.mk
index 42b3ba2..dd24664 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -20,6 +20,7 @@
 LOCAL_SDK_VERSION := 4
 LOCAL_SRC_FILES := $(call all-java-files-under, donut)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -33,6 +34,7 @@
 LOCAL_SDK_VERSION := 5
 LOCAL_SRC_FILES := $(call all-java-files-under, eclair)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-donut
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -45,6 +47,7 @@
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-eclair
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -57,6 +60,7 @@
 LOCAL_SDK_VERSION := 8
 LOCAL_SRC_FILES := $(call all-java-files-under, froyo)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-eclair-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -69,6 +73,7 @@
 LOCAL_SDK_VERSION := 9
 LOCAL_SRC_FILES := $(call all-java-files-under, gingerbread)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-froyo
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -81,6 +86,7 @@
 LOCAL_SDK_VERSION := 11
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-gingerbread
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -93,6 +99,7 @@
 LOCAL_SDK_VERSION := 12
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb_mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -105,6 +112,7 @@
 LOCAL_SDK_VERSION := 13
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb_mr2)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -117,6 +125,7 @@
 LOCAL_SDK_VERSION := 14
 LOCAL_SRC_FILES := $(call all-java-files-under, ics)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb-mr2
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -129,6 +138,7 @@
 LOCAL_SDK_VERSION := 15
 LOCAL_SRC_FILES := $(call all-java-files-under, ics-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-ics
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -141,6 +151,7 @@
 LOCAL_SDK_VERSION := 16
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-ics-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -153,6 +164,7 @@
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-jellybean
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -165,6 +177,7 @@
 LOCAL_SDK_VERSION := 18
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-jellybean-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -177,6 +190,7 @@
 LOCAL_SDK_VERSION := 19
 LOCAL_SRC_FILES := $(call all-java-files-under, kitkat)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-jellybean-mr2
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -189,6 +203,7 @@
 LOCAL_SDK_VERSION := 20
 LOCAL_SRC_FILES := $(call all-java-files-under, api20)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-kitkat
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -201,6 +216,7 @@
 LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -213,6 +229,7 @@
 LOCAL_SDK_VERSION := 22
 LOCAL_SRC_FILES := $(call all-java-files-under, api22)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api21
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -225,6 +242,20 @@
 LOCAL_SDK_VERSION := 23
 LOCAL_SRC_FILES := $(call all-java-files-under, api23)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api22
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# -----------------------------------------------------------------------
+
+# A helper sub-library that makes direct use of V24 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api23
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -238,7 +269,8 @@
 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-api23
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api24
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v4/NOTICES.md b/v4/NOTICES.md
new file mode 100644
index 0000000..8665d5d
--- /dev/null
+++ b/v4/NOTICES.md
@@ -0,0 +1,12 @@
+# Change Log
+
+## [23.0.1](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/) (2015-09-24)
+
+**Breakage and deprecation notices:**
+
+- ExploreByTouchHelper
+  - Several public methods that are only meant to be called by app developers (and not internally by
+    the helper itself) became final. Any code that depends on overriding these methods should be
+    moved elsewhere.
+  - The concept of keyboard and accessibility focus have been clarified. As a result, the
+    getFocusedVirtualView() method has been deprecated and will be removed in a subsequent release.
diff --git a/v4/api/current.txt b/v4/api/current.txt
index ce5e0e5..5abb489 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -299,10 +299,12 @@
     method public void noteStateNotSaved();
     method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
     method public void reportLoaderStart();
-    method public void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+    method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+    method public void restoreAllState(android.os.Parcelable, android.support.v4.app.FragmentManagerNonConfig);
     method public void restoreLoaderNonConfig(android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager>);
     method public android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager> retainLoaderNonConfig();
-    method public java.util.List<android.support.v4.app.Fragment> retainNonConfig();
+    method public android.support.v4.app.FragmentManagerNonConfig retainNestedNonConfig();
+    method public deprecated java.util.List<android.support.v4.app.Fragment> retainNonConfig();
     method public android.os.Parcelable saveAllState();
   }
 
@@ -361,6 +363,9 @@
     method public abstract void onBackStackChanged();
   }
 
+  public class FragmentManagerNonConfig {
+  }
+
   public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
     ctor public FragmentPagerAdapter(android.support.v4.app.FragmentManager);
     method public abstract android.support.v4.app.Fragment getItem(int);
@@ -393,6 +398,8 @@
     method public abstract android.support.v4.app.FragmentTransaction attach(android.support.v4.app.Fragment);
     method public abstract int commit();
     method public abstract int commitAllowingStateLoss();
+    method public abstract void commitNow();
+    method public abstract void commitNowAllowingStateLoss();
     method public abstract android.support.v4.app.FragmentTransaction detach(android.support.v4.app.Fragment);
     method public abstract android.support.v4.app.FragmentTransaction disallowAddToBackStack();
     method public abstract android.support.v4.app.FragmentTransaction hide(android.support.v4.app.Fragment);
@@ -920,14 +927,17 @@
   public class ContextCompat {
     ctor public ContextCompat();
     method public static int checkSelfPermission(android.content.Context, java.lang.String);
+    method public static android.content.Context createDeviceProtectedStorageContext(android.content.Context);
     method public static java.io.File getCodeCacheDir(android.content.Context);
     method public static final int getColor(android.content.Context, int);
     method public static final android.content.res.ColorStateList getColorStateList(android.content.Context, int);
+    method public static java.io.File getDataDir(android.content.Context);
     method public static final android.graphics.drawable.Drawable getDrawable(android.content.Context, int);
     method public static java.io.File[] getExternalCacheDirs(android.content.Context);
     method public static java.io.File[] getExternalFilesDirs(android.content.Context, java.lang.String);
     method public final java.io.File getNoBackupFilesDir(android.content.Context);
     method public static java.io.File[] getObbDirs(android.content.Context);
+    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);
   }
@@ -1070,6 +1080,13 @@
 
 package android.support.v4.content.res {
 
+  public final class ConfigurationHelper {
+    method public static int getDensityDpi(android.content.res.Resources);
+    method public static int getScreenHeightDp(android.content.res.Resources);
+    method public static int getScreenWidthDp(android.content.res.Resources);
+    method public static int getSmallestScreenWidthDp(android.content.res.Resources);
+  }
+
   public final class ResourcesCompat {
     method public static int getColor(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
     method public static android.content.res.ColorStateList getColorStateList(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
@@ -1284,6 +1301,9 @@
     ctor public MediaBrowserServiceCompat.BrowserRoot(java.lang.String, android.os.Bundle);
     method public android.os.Bundle getExtras();
     method public java.lang.String getRootId();
+    field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+    field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
+    field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
   }
 
   public static class MediaBrowserServiceCompat.Result {
@@ -1555,6 +1575,10 @@
     method public abstract void playFromMediaId(java.lang.String, android.os.Bundle);
     method public abstract void playFromSearch(java.lang.String, android.os.Bundle);
     method public abstract void playFromUri(android.net.Uri, android.os.Bundle);
+    method public abstract void prepare();
+    method public abstract void prepareFromMediaId(java.lang.String, android.os.Bundle);
+    method public abstract void prepareFromSearch(java.lang.String, android.os.Bundle);
+    method public abstract void prepareFromUri(android.net.Uri, android.os.Bundle);
     method public abstract void rewind();
     method public abstract void seekTo(long);
     method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction, android.os.Bundle);
@@ -1608,6 +1632,10 @@
     method public void onPlayFromMediaId(java.lang.String, android.os.Bundle);
     method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
     method public void onPlayFromUri(android.net.Uri, android.os.Bundle);
+    method public void onPrepare();
+    method public void onPrepareFromMediaId(java.lang.String, android.os.Bundle);
+    method public void onPrepareFromSearch(java.lang.String, android.os.Bundle);
+    method public void onPrepareFromUri(android.net.Uri, android.os.Bundle);
     method public void onRewind();
     method public void onSeekTo(long);
     method public void onSetRating(android.support.v4.media.RatingCompat);
@@ -1676,6 +1704,10 @@
     field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
     field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
     field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+    field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+    field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+    field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+    field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
     field public static final long ACTION_REWIND = 8L; // 0x8L
     field public static final long ACTION_SEEK_TO = 256L; // 0x100L
     field public static final long ACTION_SET_RATING = 128L; // 0x80L
@@ -1747,7 +1779,9 @@
     method public static void incrementOperationCount(int);
     method public static void incrementOperationCount(int, int);
     method public static void setThreadStatsTag(int);
+    method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
     method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
+    method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
     method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
   }
 
@@ -1759,6 +1793,10 @@
     method public static android.os.AsyncTask<Params, Progress, Result> executeParallel(android.os.AsyncTask<Params, Progress, Result>, Params...);
   }
 
+  public class BuildCompat {
+    method public static boolean isAtLeastN();
+  }
+
   public final class CancellationSignal {
     ctor public CancellationSignal();
     method public void cancel();
@@ -1796,6 +1834,11 @@
     method public static void endSection();
   }
 
+  public class UserManagerCompat {
+    ctor public UserManagerCompat();
+    method public static boolean isUserUnlocked(android.content.Context);
+  }
+
 }
 
 package android.support.v4.print {
@@ -2109,6 +2152,15 @@
     method public abstract void onActionProviderVisibilityChanged(boolean);
   }
 
+  public final class AsyncLayoutInflater {
+    ctor public AsyncLayoutInflater(android.content.Context);
+    method public void inflate(int, android.view.ViewGroup, android.support.v4.view.AsyncLayoutInflater.OnInflateFinishedListener);
+  }
+
+  public static abstract interface AsyncLayoutInflater.OnInflateFinishedListener {
+    method public abstract void onInflateFinished(android.view.View, int, android.view.ViewGroup);
+  }
+
   public final class GestureDetectorCompat {
     ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
     ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler);
@@ -2157,6 +2209,7 @@
     method public static java.lang.Object getKeyDispatcherState(android.view.View);
     method public static boolean hasModifiers(android.view.KeyEvent, int);
     method public static boolean hasNoModifiers(android.view.KeyEvent);
+    method public static boolean isCtrlPressed(android.view.KeyEvent);
     method public static boolean isTracking(android.view.KeyEvent);
     method public static boolean metaStateHasModifiers(int, int);
     method public static boolean metaStateHasNoModifiers(int);
@@ -2217,11 +2270,13 @@
     method public static int getActionMasked(android.view.MotionEvent);
     method public static float getAxisValue(android.view.MotionEvent, int);
     method public static float getAxisValue(android.view.MotionEvent, int, int);
+    method public static int getButtonState(android.view.MotionEvent);
     method public static int getPointerCount(android.view.MotionEvent);
     method public static int getPointerId(android.view.MotionEvent, int);
     method public static int getSource(android.view.MotionEvent);
     method public static float getX(android.view.MotionEvent, int);
     method public static float getY(android.view.MotionEvent, int);
+    method public static boolean isFromSource(android.view.MotionEvent, int);
     field public static final int ACTION_HOVER_ENTER = 9; // 0x9
     field public static final int ACTION_HOVER_EXIT = 10; // 0xa
     field public static final int ACTION_HOVER_MOVE = 7; // 0x7
@@ -2256,6 +2311,8 @@
     field public static final int AXIS_LTRIGGER = 17; // 0x11
     field public static final int AXIS_ORIENTATION = 8; // 0x8
     field public static final int AXIS_PRESSURE = 2; // 0x2
+    field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+    field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
     field public static final int AXIS_RTRIGGER = 18; // 0x12
     field public static final int AXIS_RUDDER = 20; // 0x14
     field public static final int AXIS_RX = 12; // 0xc
@@ -2273,6 +2330,7 @@
     field public static final int AXIS_X = 0; // 0x0
     field public static final int AXIS_Y = 1; // 0x1
     field public static final int AXIS_Z = 11; // 0xb
+    field public static final int BUTTON_PRIMARY = 1; // 0x1
   }
 
   public abstract interface NestedScrollingChild {
@@ -2372,6 +2430,35 @@
     method public void setTextSpacing(int);
   }
 
+  public final class PointerIconCompat {
+    method public static android.support.v4.view.PointerIconCompat createCustomIcon(android.graphics.Bitmap, float, float);
+    method public static android.support.v4.view.PointerIconCompat getSystemIcon(android.content.Context, int);
+    method public static android.support.v4.view.PointerIconCompat loadCustomIcon(android.content.res.Resources, int);
+    field public static final int STYLE_ALIAS = 1010; // 0x3f2
+    field public static final int STYLE_ALL_SCROLL = 1013; // 0x3f5
+    field public static final int STYLE_ARROW = 1000; // 0x3e8
+    field public static final int STYLE_CELL = 1006; // 0x3ee
+    field public static final int STYLE_CONTEXT_MENU = 1001; // 0x3e9
+    field public static final int STYLE_COPY = 1011; // 0x3f3
+    field public static final int STYLE_CROSSHAIR = 1007; // 0x3ef
+    field public static final int STYLE_DEFAULT = 1000; // 0x3e8
+    field public static final int STYLE_GRAB = 1020; // 0x3fc
+    field public static final int STYLE_GRABBING = 1021; // 0x3fd
+    field public static final int STYLE_HAND = 1002; // 0x3ea
+    field public static final int STYLE_HELP = 1003; // 0x3eb
+    field public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+    field public static final int STYLE_NO_DROP = 1012; // 0x3f4
+    field public static final int STYLE_NULL = 0; // 0x0
+    field public static final int STYLE_TEXT = 1008; // 0x3f0
+    field public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+    field public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+    field public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+    field public static final int STYLE_VERTICAL_TEXT = 1009; // 0x3f1
+    field public static final int STYLE_WAIT = 1004; // 0x3ec
+    field public static final int STYLE_ZOOM_IN = 1018; // 0x3fa
+    field public static final int STYLE_ZOOM_OUT = 1019; // 0x3fb
+  }
+
   public final class ScaleGestureDetectorCompat {
     method public static boolean isQuickScaleEnabled(java.lang.Object);
     method public static void setQuickScaleEnabled(java.lang.Object, boolean);
@@ -2398,7 +2485,8 @@
     method public static float getYVelocity(android.view.VelocityTracker, int);
   }
 
-  public final class ViewCompat {
+  public class ViewCompat {
+    ctor protected ViewCompat();
     method public static android.support.v4.view.ViewPropertyAnimatorCompat animate(android.view.View);
     method public static boolean canScrollHorizontally(android.view.View, int);
     method public static boolean canScrollVertically(android.view.View, int);
@@ -2451,6 +2539,7 @@
     method public static boolean hasNestedScrollingParent(android.view.View);
     method public static boolean hasOnClickListeners(android.view.View);
     method public static boolean hasOverlappingRendering(android.view.View);
+    method public static boolean hasPointerCapture(android.view.View);
     method public static boolean hasTransientState(android.view.View);
     method public static boolean isAttachedToWindow(android.view.View);
     method public static boolean isLaidOut(android.view.View);
@@ -2469,6 +2558,7 @@
     method public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
     method public static void postOnAnimation(android.view.View, java.lang.Runnable);
     method public static void postOnAnimationDelayed(android.view.View, java.lang.Runnable, long);
+    method public static void releasePointerCapture(android.view.View);
     method public static void requestApplyInsets(android.view.View);
     method public static int resolveSizeAndState(int, int, int);
     method public static void setAccessibilityDelegate(android.view.View, android.support.v4.view.AccessibilityDelegateCompat);
@@ -2493,6 +2583,8 @@
     method public static void setPaddingRelative(android.view.View, int, int, int, int);
     method public static void setPivotX(android.view.View, float);
     method public static void setPivotY(android.view.View, float);
+    method public static void setPointerCapture(android.view.View);
+    method public static void setPointerIcon(android.view.View, android.support.v4.view.PointerIconCompat);
     method public static void setRotation(android.view.View, float);
     method public static void setRotationX(android.view.View, float);
     method public static void setRotationY(android.view.View, float);
@@ -2796,6 +2888,7 @@
     method public java.lang.CharSequence getPackageName();
     method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getParent();
     method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat getRangeInfo();
+    method public java.lang.CharSequence getRoleDescription();
     method public java.lang.CharSequence getText();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
@@ -2864,6 +2957,7 @@
     method public void setParent(android.view.View, int);
     method public void setPassword(boolean);
     method public void setRangeInfo(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat);
+    method public void setRoleDescription(java.lang.CharSequence);
     method public void setScrollable(boolean);
     method public void setSelected(boolean);
     method public void setSource(android.view.View);
@@ -3241,17 +3335,26 @@
 
   public abstract class ExploreByTouchHelper extends android.support.v4.view.AccessibilityDelegateCompat {
     ctor public ExploreByTouchHelper(android.view.View);
-    method public boolean dispatchHoverEvent(android.view.MotionEvent);
-    method public int getFocusedVirtualView();
+    method public final boolean clearKeyboardFocusForVirtualView(int);
+    method public final boolean dispatchHoverEvent(android.view.MotionEvent);
+    method public final boolean dispatchKeyEvent(android.view.KeyEvent);
+    method public final int getAccessibilityFocusedVirtualViewId();
+    method public deprecated int getFocusedVirtualView();
+    method public final int getKeyboardFocusedVirtualViewId();
     method protected abstract int getVirtualViewAt(float, float);
     method protected abstract void getVisibleVirtualViews(java.util.List<java.lang.Integer>);
-    method public void invalidateRoot();
-    method public void invalidateVirtualView(int);
+    method public final void invalidateRoot();
+    method public final void invalidateVirtualView(int);
+    method public final void invalidateVirtualView(int, int);
+    method public final void onFocusChanged(boolean, int, android.graphics.Rect);
     method protected abstract boolean onPerformActionForVirtualView(int, int, android.os.Bundle);
-    method protected abstract void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
-    method public void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+    method protected void onPopulateEventForHost(android.view.accessibility.AccessibilityEvent);
+    method protected void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
+    method protected void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
     method protected abstract void onPopulateNodeForVirtualView(int, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
-    method public boolean sendEventForVirtualView(int, int);
+    method protected void onVirtualViewKeyboardFocusChanged(int, boolean);
+    method public final boolean requestKeyboardFocusForVirtualView(int);
+    method public final boolean sendEventForVirtualView(int, int);
     field public static final int HOST_ID = -1; // 0xffffffff
     field public static final int INVALID_ID = -2147483648; // 0x80000000
   }
diff --git a/v4/api/removed.txt b/v4/api/removed.txt
index e69de29..98f8c44 100644
--- a/v4/api/removed.txt
+++ b/v4/api/removed.txt
@@ -0,0 +1,18 @@
+package android.support.v4.content {
+
+  public class ContextCompat {
+    method public static deprecated android.content.Context createDeviceEncryptedStorageContext(android.content.Context);
+    method public static deprecated boolean isDeviceEncryptedStorage(android.content.Context);
+  }
+
+}
+
+package android.support.v4.os {
+
+  public class UserManagerCompat {
+    method public static deprecated boolean isUserRunningAndLocked(android.content.Context);
+    method public static deprecated boolean isUserRunningAndUnlocked(android.content.Context);
+  }
+
+}
+
diff --git a/v4/api20/android/support/v4/print/PrintHelperApi20.java b/v4/api20/android/support/v4/print/PrintHelperApi20.java
new file mode 100644
index 0000000..ce62106
--- /dev/null
+++ b/v4/api20/android/support/v4/print/PrintHelperApi20.java
@@ -0,0 +1,33 @@
+/*
+ * 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.print;
+
+import android.content.Context;
+
+/**
+ * Api20 specific PrintManager API implementation.
+ */
+class PrintHelperApi20 extends PrintHelperKitkat {
+    PrintHelperApi20(Context context) {
+        super(context);
+
+        /**
+         * There is a bug in the PrintActivity that causes it to ignore the orientation
+         */
+        mPrintActivityRespectsOrientation = false;
+    }
+}
\ No newline at end of file
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
index ddcff2e..37c4b97 100644
--- a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -92,6 +92,36 @@
         return transitionSet;
     }
 
+    private static void excludeViews(Transition transition, Transition fromTransition,
+            ArrayList<View> views, boolean exclude) {
+        if (transition != null) {
+            final int viewCount = fromTransition == null ? 0 : views.size();
+            for (int i = 0; i < viewCount; i++) {
+                transition.excludeTarget(views.get(i), exclude);
+            }
+        }
+    }
+
+    /**
+     * Exclude (or remove the exclude) of shared element views from the enter and exit transitions.
+     *
+     * @param enterTransitionObj The enter transition
+     * @param exitTransitionObj The exit transition
+     * @param sharedElementTransitionObj The shared element transition
+     * @param views The shared element target views.
+     * @param exclude <code>true</code> to exclude or <code>false</code> to remove the excluded
+     *                views.
+     */
+    public static void excludeSharedElementViews(Object enterTransitionObj,
+            Object exitTransitionObj, Object sharedElementTransitionObj, ArrayList<View> views,
+            boolean exclude) {
+        Transition enterTransition = (Transition) enterTransitionObj;
+        Transition exitTransition = (Transition) exitTransitionObj;
+        Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
+        excludeViews(enterTransition, sharedElementTransition, views, exclude);
+        excludeViews(exitTransition, sharedElementTransition, views, exclude);
+    }
+
     /**
      * Prepares the enter transition by adding a non-existent view to the transition's target list
      * and setting it epicenter callback. By adding a non-existent view to the target list,
@@ -104,35 +134,42 @@
      *  capturing the final state of the Transition.</p>
      */
     public static void addTransitionTargets(Object enterTransitionObject,
-            Object sharedElementTransitionObject, final View container,
+            Object sharedElementTransitionObject, Object exitTransitionObject, final View container,
             final ViewRetriever inFragment, final View nonExistentView,
             EpicenterView epicenterView, final Map<String, String> nameOverrides,
-            final ArrayList<View> enteringViews, final Map<String, View> namedViews,
-            final Map<String, View> renamedViews, final ArrayList<View> sharedElementTargets) {
+            final ArrayList<View> enteringViews, final ArrayList<View> exitingViews,
+            final Map<String, View> namedViews, final Map<String, View> renamedViews,
+            final ArrayList<View> sharedElementTargets) {
+        final Transition enterTransition = (Transition) enterTransitionObject;
+        final Transition exitTransition = (Transition) exitTransitionObject;
+        final Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
+        excludeViews(enterTransition, exitTransition, exitingViews, true);
         if (enterTransitionObject != null || sharedElementTransitionObject != null) {
-            final Transition enterTransition = (Transition) enterTransitionObject;
             if (enterTransition != null) {
                 enterTransition.addTarget(nonExistentView);
             }
             if (sharedElementTransitionObject != null) {
-                setSharedElementTargets(sharedElementTransitionObject, nonExistentView,
+                setSharedElementTargets(sharedElementTransition, nonExistentView,
                         namedViews, sharedElementTargets);
+                excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
+                excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
             }
 
-            if (inFragment != null) {
-                container.getViewTreeObserver().addOnPreDrawListener(
-                        new ViewTreeObserver.OnPreDrawListener() {
-                            public boolean onPreDraw() {
-                                container.getViewTreeObserver().removeOnPreDrawListener(this);
-                                if (enterTransition != null) {
-                                    enterTransition.removeTarget(nonExistentView);
-                                }
+            container.getViewTreeObserver().addOnPreDrawListener(
+                    new ViewTreeObserver.OnPreDrawListener() {
+                        public boolean onPreDraw() {
+                            container.getViewTreeObserver().removeOnPreDrawListener(this);
+                            if (enterTransition != null) {
+                                enterTransition.removeTarget(nonExistentView);
+                            }
+                            if (inFragment != null) {
                                 View fragmentView = inFragment.getView();
                                 if (fragmentView != null) {
                                     if (!nameOverrides.isEmpty()) {
                                         findNamedViews(renamedViews, fragmentView);
                                         renamedViews.keySet().retainAll(nameOverrides.values());
-                                        for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
+                                        for (Map.Entry<String, String> entry : nameOverrides
+                                                .entrySet()) {
                                             String to = entry.getValue();
                                             View view = renamedViews.get(to);
                                             if (view != null) {
@@ -148,10 +185,12 @@
                                         addTargets(enterTransition, enteringViews);
                                     }
                                 }
-                                return true;
                             }
-                        });
-            }
+                            excludeViews(exitTransition, enterTransition, enteringViews, true);
+
+                            return true;
+                        }
+                    });
             setSharedElementEpicenter(enterTransition, epicenterView);
         }
     }
@@ -355,9 +394,15 @@
                     sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
                     if (enterTransition != null) {
                         removeTargets(enterTransition, enteringViews);
+                        excludeViews(enterTransition, exitTransition, exitingViews, false);
+                        excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
+                                false);
                     }
                     if (exitTransition != null) {
                         removeTargets(exitTransition, exitingViews);
+                        excludeViews(exitTransition, enterTransition, enteringViews, false);
+                        excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
+                                false);
                     }
                     if (sharedElementTransition != null) {
                         removeTargets(sharedElementTransition, sharedElementTargets);
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java b/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
deleted file mode 100644
index 8db5fcb..0000000
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
+++ /dev/null
@@ -1,119 +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.media;
-
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-
-/**
- * An adapter class for replacing the auto generated hidden class, IMediaBrowserService.Stub
- */
-class IMediaBrowserServiceAdapterApi21 {
-    static abstract class Stub extends Binder implements IInterface {
-        private static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
-        // Following TRANSACTION_XXX values are synchronized with the auto generated java file
-        // from IMediaBrowserService.aidl
-        private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
-        private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
-        private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
-        private static final int TRANSACTION_removeSubscription =
-                IBinder.FIRST_CALL_TRANSACTION + 3;
-        private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
-
-        public Stub() {
-            attachInterface(this, DESCRIPTOR);
-        }
-
-        @Override
-        public IBinder asBinder() {
-            return this;
-        }
-
-        @Override
-        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-                throws RemoteException {
-            switch (code) {
-                case IBinder.INTERFACE_TRANSACTION: {
-                    reply.writeString(DESCRIPTOR);
-                    return true;
-                }
-                case TRANSACTION_connect: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Bundle arg1;
-                    if (data.readInt() != 0) {
-                        arg1 = Bundle.CREATOR.createFromParcel(data);
-                    } else {
-                        arg1 = null;
-                    }
-                    Object arg2 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    connect(arg0, arg1, arg2);
-                    return true;
-                }
-                case TRANSACTION_disconnect: {
-                    data.enforceInterface(DESCRIPTOR);
-                    Object arg0 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    disconnect(arg0);
-                    return true;
-                }
-                case TRANSACTION_addSubscription: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    addSubscription(arg0, arg1);
-                    return true;
-                }
-                case TRANSACTION_removeSubscription: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    removeSubscription(arg0, arg1);
-                    return true;
-                }
-                case TRANSACTION_getMediaItem: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    ResultReceiver arg1;
-                    if (data.readInt() != 0) {
-                        arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
-                    } else {
-                        arg1 = null;
-                    }
-                    getMediaItem(arg0, arg1);
-                    return true;
-                }
-            }
-            return super.onTransact(code, data, reply, flags);
-        }
-
-        public abstract void connect(final String pkg, final Bundle rootHints,
-                final Object callbacks);
-        public abstract void disconnect(final Object callbacks);
-        public abstract void addSubscription(final String id, final Object callbacks);
-        public abstract void removeSubscription(final String id, final Object callbacks);
-        public abstract void getMediaItem(final String mediaId, final ResultReceiver receiver);
-    }
-}
diff --git a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
index c6ccb14..3dc4e7c 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -129,25 +129,26 @@
         @Override
         public void onChildrenLoaded(@NonNull String parentId,
                 List<MediaBrowser.MediaItem> children) {
-            List<Parcel> parcelList = null;
-            if (children != null && children.size() == 1
-                    && children.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID)) {
-                children = null;
-            }
-            if (children != null) {
-                parcelList = new ArrayList<>();
-                for (MediaBrowser.MediaItem item : children) {
-                    Parcel parcel = Parcel.obtain();
-                    item.writeToParcel(parcel, 0);
-                    parcelList.add(parcel);
-                }
-            }
-            mSubscriptionCallback.onChildrenLoaded(parentId, parcelList);
+            mSubscriptionCallback.onChildrenLoaded(parentId, itemListToParcelList(children));
         }
 
         @Override
         public void onError(@NonNull String parentId) {
             mSubscriptionCallback.onError(parentId);
         }
+
+        static List<Parcel> itemListToParcelList(List<MediaBrowser.MediaItem> itemList) {
+            if (itemList == null || (itemList.size() == 1
+                    && itemList.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID))) {
+                return null;
+            }
+            List<Parcel> parcelList = new ArrayList<>();
+            for (MediaBrowser.MediaItem item : itemList) {
+                Parcel parcel = Parcel.obtain();
+                item.writeToParcel(parcel, 0);
+                parcelList.add(parcel);
+            }
+            return parcelList;
+        }
     }
 }
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index a09a74b..1538350 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -19,18 +19,27 @@
 import android.content.Intent;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 
 import java.util.ArrayList;
 import java.util.List;
 
 class MediaBrowserServiceCompatApi21 {
+    private static Object sNullParceledListSliceObj;
+    static {
+        MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
+                MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
+        MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
+        List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
+        nullMediaItemList.add(nullMediaItem);
+        sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
+    }
 
     public static Object createService() {
         return new MediaBrowserServiceAdaptorApi21();
@@ -44,35 +53,41 @@
         return ((MediaBrowserServiceAdaptorApi21) serviceObj).onBind(intent);
     }
 
-    public interface ServiceImplApi21 {
-        void connect(final String pkg, final Bundle rootHints, final ServiceCallbacks callbacks);
-        void disconnect(final ServiceCallbacks callbacks);
-        void addSubscription(final String id, final ServiceCallbacks callbacks);
-        void removeSubscription(final String id, final ServiceCallbacks callbacks);
+    public static Object parcelListToParceledListSliceObject(List<Parcel> list) {
+        if (list == null) {
+            if (Build.VERSION.SDK_INT <= 23) {
+                return sNullParceledListSliceObj;
+            }
+            return null;
+        }
+        List<MediaBrowser.MediaItem> itemList = new ArrayList<>();
+        for (Parcel parcel : list) {
+            parcel.setDataPosition(0);
+            itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+            parcel.recycle();
+        }
+        return ParceledListSliceAdapterApi21.newInstance(itemList);
     }
 
-    public interface ServiceCallbacks {
+    public interface ServiceImplApi21 {
+        void connect(String pkg, Bundle rootHints, ServiceCallbacksApi21 callbacks);
+        void disconnect(ServiceCallbacksApi21 callbacks);
+        void addSubscription(String id, ServiceCallbacksApi21 callbacks);
+        void removeSubscription(String id, ServiceCallbacksApi21 callbacks);
+    }
+
+    public interface ServiceCallbacksApi21 {
         IBinder asBinder();
         void onConnect(String root, Object session, Bundle extras) throws RemoteException;
         void onConnectFailed() throws RemoteException;
         void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException;
     }
 
-    public static class ServiceCallbacksApi21 implements ServiceCallbacks {
-        private static Object sNullParceledListSliceObj;
-        static {
-            MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
-                    MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
-            MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
-            List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
-            nullMediaItemList.add(nullMediaItem);
-            sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
-        }
+    public static class ServiceCallbacksImplApi21 implements ServiceCallbacksApi21 {
+        final ServiceCallbacksAdapterApi21 mCallbacks;
 
-        private final IMediaBrowserServiceCallbacksAdapterApi21 mCallbacks;
-
-        ServiceCallbacksApi21(Object callbacksObj) {
-            mCallbacks = new IMediaBrowserServiceCallbacksAdapterApi21(callbacksObj);
+        ServiceCallbacksImplApi21(Object callbacksObj) {
+            mCallbacks = createCallbacks(callbacksObj);
         }
 
         public IBinder asBinder() {
@@ -88,31 +103,19 @@
         }
 
         public void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException {
-            List<MediaBrowser.MediaItem> itemList = null;
-            if (list != null) {
-                itemList = new ArrayList<>();
-                for (Parcel parcel : list) {
-                    parcel.setDataPosition(0);
-                    itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
-                    parcel.recycle();
-                }
-            }
-            Object pls;
-            if (Build.VERSION.SDK_INT > 23) {
-                pls = itemList == null ? null : ParceledListSliceAdapterApi21.newInstance(itemList);
-            } else {
-                pls = itemList == null ? sNullParceledListSliceObj
-                        : ParceledListSliceAdapterApi21.newInstance(itemList);
-            }
-            mCallbacks.onLoadChildren(mediaId, pls);
+            mCallbacks.onLoadChildren(mediaId, parcelListToParceledListSliceObject(list));
+        }
+
+        ServiceCallbacksAdapterApi21 createCallbacks(Object callbacksObj) {
+            return new ServiceCallbacksAdapterApi21(callbacksObj);
         }
     }
 
     static class MediaBrowserServiceAdaptorApi21 {
-        ServiceBinderProxyApi21 mBinder;
+        Binder mBinder;
 
         public void onCreate(ServiceImplApi21 serviceImpl) {
-            mBinder = new ServiceBinderProxyApi21(serviceImpl);
+            mBinder = createServiceBinder(serviceImpl);
         }
 
         public IBinder onBind(Intent intent) {
@@ -122,39 +125,8 @@
             return null;
         }
 
-        static class ServiceBinderProxyApi21 extends IMediaBrowserServiceAdapterApi21.Stub {
-            final ServiceImplApi21 mServiceImpl;
-
-            ServiceBinderProxyApi21(ServiceImplApi21 serviceImpl) {
-                super();
-                mServiceImpl = serviceImpl;
-            }
-
-            @Override
-            public void connect(final String pkg, final Bundle rootHints, final Object callbacks) {
-                mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void disconnect(final Object callbacks) {
-                mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void addSubscription(final String id, final Object callbacks) {
-                mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void removeSubscription(final String id,
-                    final Object callbacks) {
-                mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-                // No operation since this method is added in API 23.
-            }
+        protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+            return new ServiceBinderAdapterApi21(serviceImpl);
         }
     }
 }
diff --git a/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java b/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java
new file mode 100644
index 0000000..9495049
--- /dev/null
+++ b/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java
@@ -0,0 +1,125 @@
+/*
+ * 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.media;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * A class for presenting a service binder for API 21.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 21.
+ */
+class ServiceBinderAdapterApi21 extends Binder implements IInterface {
+    // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+    // from IMediaBrowserService.aidl
+    private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
+    private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
+    private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
+    private static final int TRANSACTION_removeSubscription =
+            IBinder.FIRST_CALL_TRANSACTION + 3;
+
+    static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
+    final MediaBrowserServiceCompatApi21.ServiceImplApi21 mServiceImpl;
+
+    public ServiceBinderAdapterApi21(
+            MediaBrowserServiceCompatApi21.ServiceImplApi21 serviceImpl) {
+        mServiceImpl = serviceImpl;
+        attachInterface(this, DESCRIPTOR);
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return this;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+            case IBinder.INTERFACE_TRANSACTION: {
+                reply.writeString(DESCRIPTOR);
+                return true;
+            }
+            case TRANSACTION_connect: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Bundle arg1;
+                if (data.readInt() != 0) {
+                    arg1 = Bundle.CREATOR.createFromParcel(data);
+                } else {
+                    arg1 = null;
+                }
+                Object arg2 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                connect(arg0, arg1, arg2);
+                return true;
+            }
+            case TRANSACTION_disconnect: {
+                data.enforceInterface(DESCRIPTOR);
+                Object arg0 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                disconnect(arg0);
+                return true;
+            }
+            case TRANSACTION_addSubscription: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Object arg1 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                addSubscription(arg0, arg1);
+                return true;
+            }
+            case TRANSACTION_removeSubscription: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Object arg1 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                removeSubscription(arg0, arg1);
+                return true;
+            }
+        }
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    void connect(String pkg, Bundle rootHints, Object callbacks) {
+        mServiceImpl.connect(pkg, rootHints,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void disconnect(Object callbacks) {
+        mServiceImpl.disconnect(
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void addSubscription(String id, Object callbacks) {
+        mServiceImpl.addSubscription(id,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void removeSubscription(String id, Object callbacks) {
+        mServiceImpl.removeSubscription(id,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+}
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
similarity index 96%
rename from v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
rename to v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
index b190e16..4bd7dbb 100644
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
+++ b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
@@ -17,6 +17,7 @@
 package android.support.v4.media;
 
 import android.media.session.MediaSession;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -28,15 +29,14 @@
  * An adapter class for accessing the hidden framework classes, IMediaBrowserServiceCallbacks and
  * IMediaBrowserServiceCallbacks.Stub using reflection.
  */
-class IMediaBrowserServiceCallbacksAdapterApi21 {
-
+class ServiceCallbacksAdapterApi21 {
     Object mCallbackObject;
     private Method mAsBinderMethod;
     private Method mOnConnectMethod;
     private Method mOnConnectFailedMethod;
     private Method mOnLoadChildrenMethod;
 
-    IMediaBrowserServiceCallbacksAdapterApi21(Object callbackObject) {
+    ServiceCallbacksAdapterApi21(Object callbackObject) {
         mCallbackObject = callbackObject;
         try {
             Class theClass = Class.forName("android.service.media.IMediaBrowserServiceCallbacks");
diff --git a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
index fcaea40..ebde176 100644
--- a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
+++ b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
@@ -16,26 +16,17 @@
 
 package android.support.v4.media;
 
-import android.media.browse.MediaBrowser;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Parcel;
-import android.os.ResultReceiver;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
 
 class MediaBrowserServiceCompatApi23 extends MediaBrowserServiceCompatApi21 {
-    private static final String TAG = "MediaBrowserServiceCompatApi21";
-
     public static Object createService() {
         return new MediaBrowserServiceAdaptorApi23();
     }
 
-    public static void onCreate(Object serviceObj, ServiceImplApi23 serviceImpl) {
-        ((MediaBrowserServiceAdaptorApi23) serviceObj).onCreate(serviceImpl);
-    }
-
     public interface ServiceImplApi23 extends ServiceImplApi21 {
-        void getMediaItem(final String mediaId, final ItemCallback cb);
+        void getMediaItem(String mediaId, ItemCallback cb);
     }
 
     public interface ItemCallback {
@@ -43,44 +34,8 @@
     }
 
     static class MediaBrowserServiceAdaptorApi23 extends MediaBrowserServiceAdaptorApi21 {
-
-        public void onCreate(ServiceImplApi23 serviceImpl) {
-            mBinder = new ServiceBinderProxyApi23(serviceImpl);
-        }
-
-        private static class ServiceBinderProxyApi23 extends ServiceBinderProxyApi21 {
-            ServiceImplApi23 mServiceImpl;
-
-            ServiceBinderProxyApi23(ServiceImplApi23 serviceImpl) {
-                super(serviceImpl);
-                mServiceImpl = serviceImpl;
-            }
-
-            @Override
-            public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-                final String KEY_MEDIA_ITEM;
-                try {
-                    KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
-                            "KEY_MEDIA_ITEM").get(null);
-                } catch (IllegalAccessException | NoSuchFieldException e) {
-                    Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
-                    return;
-                }
-
-                mServiceImpl.getMediaItem(mediaId, new ItemCallback() {
-                    @Override
-                    public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
-                        if (itemParcel != null) {
-                            itemParcel.setDataPosition(0);
-                            MediaBrowser.MediaItem item =
-                                    MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
-                            resultData.putParcelable(KEY_MEDIA_ITEM, item);
-                            itemParcel.recycle();
-                        }
-                        receiver.send(resultCode, resultData);
-                    }
-                });
-            }
+        protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+            return new ServiceBinderAdapterApi23((ServiceImplApi23) serviceImpl);
         }
     }
 }
diff --git a/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java b/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java
new file mode 100644
index 0000000..ba66347
--- /dev/null
+++ b/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java
@@ -0,0 +1,101 @@
+/*
+ * 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.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+/**
+ * A class for presenting a service binder for API 23.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 23.
+ */
+class ServiceBinderAdapterApi23 extends ServiceBinderAdapterApi21 {
+    private static final String TAG = "IMediaBrowserServiceAdapterApi23";
+
+    // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+    // from IMediaBrowserService.aidl
+    private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
+
+    final MediaBrowserServiceCompatApi23.ServiceImplApi23 mServiceImpl;
+
+    public ServiceBinderAdapterApi23(
+            MediaBrowserServiceCompatApi23.ServiceImplApi23 serviceImpl) {
+        super(serviceImpl);
+        mServiceImpl = serviceImpl;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return this;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+            case TRANSACTION_getMediaItem: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                ResultReceiver arg1;
+                if (data.readInt() != 0) {
+                    arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
+                } else {
+                    arg1 = null;
+                }
+                getMediaItem(arg0, arg1);
+                return true;
+            }
+        }
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    void getMediaItem(String mediaId, final ResultReceiver receiver) {
+        final String KEY_MEDIA_ITEM;
+        try {
+            KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
+                    "KEY_MEDIA_ITEM").get(null);
+        } catch (IllegalAccessException | NoSuchFieldException e) {
+            Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
+            return;
+        }
+
+        mServiceImpl.getMediaItem(mediaId, new MediaBrowserServiceCompatApi23.ItemCallback() {
+            @Override
+            public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
+                if (itemParcel != null) {
+                    itemParcel.setDataPosition(0);
+                    MediaBrowser.MediaItem item =
+                            MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
+                    resultData.putParcelable(KEY_MEDIA_ITEM, item);
+                    itemParcel.recycle();
+                }
+                receiver.send(resultCode, resultData);
+            }
+        });
+    }
+}
+
diff --git a/v4/api23/android/support/v4/print/PrintHelperApi23.java b/v4/api23/android/support/v4/print/PrintHelperApi23.java
new file mode 100644
index 0000000..5129cd3
--- /dev/null
+++ b/v4/api23/android/support/v4/print/PrintHelperApi23.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.v4.print;
+
+import android.content.Context;
+import android.print.PrintAttributes;
+
+/**
+ * Api23 specific PrintManager API implementation.
+ */
+class PrintHelperApi23 extends PrintHelperApi20 {
+    @Override
+    protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
+        return super.copyAttributes(other).setDuplexMode(other.getDuplexMode());
+    }
+
+    PrintHelperApi23(Context context) {
+        super(context);
+
+        mIsMinMarginsHandlingCorrect = false;
+    }
+}
diff --git a/v4/api24/android/support/v4/content/ContextCompatApi24.java b/v4/api24/android/support/v4/content/ContextCompatApi24.java
new file mode 100644
index 0000000..9763240
--- /dev/null
+++ b/v4/api24/android/support/v4/content/ContextCompatApi24.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content;
+
+import android.content.Context;
+
+import java.io.File;
+
+/** {@hide} */
+public class ContextCompatApi24 {
+    public static File getDataDir(Context context) {
+        return context.getDataDir();
+    }
+
+    public static Context createDeviceProtectedStorageContext(Context context) {
+        return context.createDeviceProtectedStorageContext();
+    }
+
+    public static boolean isDeviceProtectedStorage(Context context) {
+        return context.isDeviceProtectedStorage();
+    }
+}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
new file mode 100644
index 0000000..0b4f34b
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
@@ -0,0 +1,86 @@
+/*
+ * 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.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+class MediaBrowserCompatApi24 {
+    // TODO: Remove reflections once N released.
+    private static Method sSubscribeMethod;
+    private static Method sUnsubscribeMethod;
+    static {
+        try {
+            sSubscribeMethod = MediaBrowser.class.getMethod("subscribe", new Class[] {
+                    String.class, Bundle.class, MediaBrowser.SubscriptionCallback.class});
+            sUnsubscribeMethod = MediaBrowser.class.getMethod("unsubscribe",
+                    new Class[] {String.class, Bundle.class});
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static Object createSubscriptionCallback(SubscriptionCallback callback) {
+        return new SubscriptionCallbackProxy<>(callback);
+    }
+
+    public static void subscribe(
+            Object browserObj, String parentId, Bundle options, Object subscriptionCallbackObj) {
+        try {
+            sSubscribeMethod.invoke(browserObj, parentId, options, subscriptionCallbackObj);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void unsubscribe(Object browserObj, String parentId, Bundle options) {
+        try {
+            sUnsubscribeMethod.invoke(browserObj, parentId, options);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
+        void onChildrenLoaded(@NonNull String parentId, List<Parcel> children,
+                @NonNull Bundle options);
+        void onError(@NonNull String parentId, @NonNull  Bundle options);
+    }
+
+    static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
+            extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
+        public SubscriptionCallbackProxy(T callback) {
+            super(callback);
+        }
+
+        public void onChildrenLoaded(@NonNull String parentId,
+                List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
+            mSubscriptionCallback.onChildrenLoaded(
+                    parentId, itemListToParcelList(children), options);
+        }
+
+        public void onError(@NonNull String parentId, @NonNull Bundle options) {
+            mSubscriptionCallback.onError(parentId, options);
+        }
+    }
+}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
new file mode 100644
index 0000000..543776e
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
@@ -0,0 +1,66 @@
+/*
+ * 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.media;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+import java.util.List;
+
+class MediaBrowserServiceCompatApi24 extends MediaBrowserServiceCompatApi23 {
+    public static Object createService() {
+        return new MediaBrowserServiceAdaptorApi24();
+    }
+
+    public interface ServiceImplApi24 extends ServiceImplApi23 {
+        void connect(String pkg, Bundle rootHints, ServiceCallbacksApi24 callbacks);
+        void addSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
+        void removeSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
+    }
+
+    public interface ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
+        void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
+                throws RemoteException;
+    }
+
+    public static class ServiceCallbacksImplApi24 extends ServiceCallbacksImplApi21
+            implements ServiceCallbacksApi24 {
+        ServiceCallbacksImplApi24(Object callbacksObj) {
+            super(callbacksObj);
+        }
+
+        @Override
+        public void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
+                throws RemoteException {
+            ((ServiceCallbacksAdapterApi24)mCallbacks).onLoadChildrenWithOptions(
+                    mediaId, parcelListToParceledListSliceObject(list), options);
+        }
+
+        @Override
+        ServiceCallbacksAdapterApi24 createCallbacks(Object callbacksObj) {
+            return new ServiceCallbacksAdapterApi24(callbacksObj);
+        }
+    }
+
+    static class MediaBrowserServiceAdaptorApi24 extends MediaBrowserServiceAdaptorApi23 {
+        protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+            return new ServiceBinderAdapterApi24((ServiceImplApi24) serviceImpl);
+        }
+    }
+}
diff --git a/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java b/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java
new file mode 100644
index 0000000..1a6fef8
--- /dev/null
+++ b/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java
@@ -0,0 +1,97 @@
+/*
+ * 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.media;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * A class for presenting a service binder for API 24.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 24.
+ */
+class ServiceBinderAdapterApi24 extends ServiceBinderAdapterApi23 {
+    // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+    // from IMediaBrowserService.aidl
+    private static final int TRANSACTION_addSubscriptionWithOptions =
+            IBinder.FIRST_CALL_TRANSACTION + 5;
+    private static final int TRANSACTION_removeSubscriptionWithOptions =
+            IBinder.FIRST_CALL_TRANSACTION + 6;
+
+    final MediaBrowserServiceCompatApi24.ServiceImplApi24 mServiceImpl;
+
+    public ServiceBinderAdapterApi24(
+            MediaBrowserServiceCompatApi24.ServiceImplApi24 serviceImpl) {
+        super(serviceImpl);
+        mServiceImpl = serviceImpl;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return this;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+            case TRANSACTION_addSubscriptionWithOptions: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Bundle arg1 = (data.readInt() == 0)
+                        ? null : Bundle.CREATOR.createFromParcel(data);
+                Object arg2 = ServiceCallbacksAdapterApi24.Stub.asInterface(
+                        data.readStrongBinder());
+                addSubscription(arg0, arg1, arg2);
+                return true;
+            }
+            case TRANSACTION_removeSubscriptionWithOptions: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Bundle arg1 = (data.readInt() == 0)
+                        ? null : Bundle.CREATOR.createFromParcel(data);
+                Object arg2 = ServiceCallbacksAdapterApi24.Stub.asInterface(
+                        data.readStrongBinder());
+                removeSubscription(arg0, arg1, arg2);
+                return true;
+            }
+        }
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    @Override
+    void connect(String pkg, Bundle rootHints, Object callbacks) {
+        mServiceImpl.connect(pkg, rootHints,
+                new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+    }
+
+    void addSubscription(String id, Bundle options, Object callbacks) {
+        mServiceImpl.addSubscription(id, options,
+                new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+    }
+
+    void removeSubscription(String id, Bundle options, Object callbacks) {
+        mServiceImpl.removeSubscription(id, options,
+                new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+    }
+}
+
diff --git a/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java b/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java
new file mode 100644
index 0000000..741cabe
--- /dev/null
+++ b/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java
@@ -0,0 +1,53 @@
+/*
+ * 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.media;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * An adapter class for accessing the hidden framework classes, IMediaBrowserServiceCallbacks and
+ * IMediaBrowserServiceCallbacks.Stub using reflection.
+ */
+class ServiceCallbacksAdapterApi24 extends ServiceCallbacksAdapterApi21 {
+    private Method mOnLoadChildrenMethodWithOptionsMethod;
+
+    ServiceCallbacksAdapterApi24(Object callbackObject) {
+        super(callbackObject);
+        try {
+            Class theClass = Class.forName("android.service.media.IMediaBrowserServiceCallbacks");
+            Class parceledListSliceClass = Class.forName("android.content.pm.ParceledListSlice");
+            mOnLoadChildrenMethodWithOptionsMethod = theClass.getMethod("onLoadChildrenWithOptions",
+                    new Class[] { String.class, parceledListSliceClass, Bundle.class });
+        } catch (ClassNotFoundException | NoSuchMethodException e) {
+            e.printStackTrace();
+        }
+    }
+
+    void onLoadChildrenWithOptions(String mediaId, Object parceledListSliceObj, Bundle options)
+            throws RemoteException {
+        try {
+            mOnLoadChildrenMethodWithOptionsMethod.invoke(
+                    mCallbackObject, mediaId, parceledListSliceObj, options);
+        } catch (IllegalAccessException | InvocationTargetException | NullPointerException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java b/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
new file mode 100644
index 0000000..04bf843
--- /dev/null
+++ b/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
@@ -0,0 +1,42 @@
+/*
+ * 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.media.session;
+
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+
+class MediaControllerCompatApi24 {
+
+    public static class TransportControls extends MediaControllerCompatApi23.TransportControls {
+        public static void prepare(Object controlsObj) {
+            ((MediaController.TransportControls) controlsObj).prepare();
+        }
+
+        public static void prepareFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromMediaId(mediaId, extras);
+        }
+
+        public static void prepareFromSearch(Object controlsObj, String query, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromSearch(query, extras);
+        }
+
+        public static void prepareFromUri(Object controlsObj, Uri uri, Bundle extras) {
+            ((MediaController.TransportControls) controlsObj).prepareFromUri(uri, extras);
+        }
+    }
+}
diff --git a/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java b/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
new file mode 100644
index 0000000..e03a3ee
--- /dev/null
+++ b/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
@@ -0,0 +1,78 @@
+/*
+ * 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.media.session;
+
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class MediaSessionCompatApi24 {
+    private static final String TAG = "MediaSessionCompatApi24";
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static String getCallingPackage(Object sessionObj) {
+        MediaSession session = (MediaSession) sessionObj;
+        try {
+            Method getCallingPackageMethod = session.getClass().getMethod("getCallingPackage");
+            return (String) getCallingPackageMethod.invoke(session);
+        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+            Log.e(TAG, "Cannot execute MediaSession.getCallingPackage()", e);
+        }
+        return null;
+    }
+
+    public interface Callback extends MediaSessionCompatApi23.Callback {
+        public void onPrepare();
+        public void onPrepareFromMediaId(String mediaId, Bundle extras);
+        public void onPrepareFromSearch(String query, Bundle extras);
+        public void onPrepareFromUri(Uri uri, Bundle extras);
+    }
+
+    static class CallbackProxy<T extends Callback>
+            extends MediaSessionCompatApi23.CallbackProxy<T> {
+        public CallbackProxy(T callback) {
+            super(callback);
+        }
+
+        @Override
+        public void onPrepare() {
+            mCallback.onPrepare();
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            mCallback.onPrepareFromMediaId(mediaId, extras);
+        }
+
+        @Override
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            mCallback.onPrepareFromSearch(query, extras);
+        }
+
+        @Override
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            mCallback.onPrepareFromUri(uri, extras);
+        }
+    }
+}
diff --git a/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
new file mode 100644
index 0000000..f525dea
--- /dev/null
+++ b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.net;
+
+import android.net.TrafficStats;
+
+import java.net.DatagramSocket;
+import java.net.SocketException;
+
+/** {@hide} */
+public class TrafficStatsCompatApi24 {
+    public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+        TrafficStats.tagDatagramSocket(socket);
+    }
+
+    public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+        TrafficStats.untagDatagramSocket(socket);
+    }
+}
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
similarity index 67%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v4/api24/android/support/v4/os/UserManagerCompatApi24.java
index 8126b38..7067e01 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
+package android.support.v4.os;
 
-import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
 
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+/** {@hide} */
+public class UserManagerCompatApi24 {
+    public static boolean isUserUnlocked(Context context) {
+        return context.getSystemService(UserManager.class).isUserUnlocked();
+    }
 }
diff --git a/v4/api24/android/support/v4/print/PrintHelperApi24.java b/v4/api24/android/support/v4/print/PrintHelperApi24.java
new file mode 100644
index 0000000..3815941
--- /dev/null
+++ b/v4/api24/android/support/v4/print/PrintHelperApi24.java
@@ -0,0 +1,31 @@
+/*
+ * 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.print;
+
+import android.content.Context;
+
+/**
+ * Api24 specific PrintManager API implementation.
+ */
+class PrintHelperApi24 extends PrintHelperApi23 {
+    PrintHelperApi24(Context context) {
+        super(context);
+
+        mIsMinMarginsHandlingCorrect = true;
+        mPrintActivityRespectsOrientation = true;
+    }
+}
\ No newline at end of file
diff --git a/v4/api24/android/support/v4/view/PointerIconCompatApi24.java b/v4/api24/android/support/v4/view/PointerIconCompatApi24.java
new file mode 100644
index 0000000..c9f4377
--- /dev/null
+++ b/v4/api24/android/support/v4/view/PointerIconCompatApi24.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.v4.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.view.PointerIcon;
+
+class PointerIconCompatApi24 {
+    public static Object getSystemIcon(Context context, int style) {
+        return PointerIcon.getSystemIcon(context, style);
+    }
+
+    public static Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+    }
+
+    public static Object loadCustomIcon(Resources resources, int resourceId) {
+        return PointerIcon.loadCustomIcon(resources, resourceId);
+    }
+}
diff --git a/v4/api24/android/support/v4/view/ViewCompatApi24.java b/v4/api24/android/support/v4/view/ViewCompatApi24.java
new file mode 100644
index 0000000..7e92c53
--- /dev/null
+++ b/v4/api24/android/support/v4/view/ViewCompatApi24.java
@@ -0,0 +1,38 @@
+/**
+ * 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.view;
+
+import android.view.PointerIcon;
+import android.view.View;
+
+class ViewCompatApi24 {
+    public static void setPointerCapture(View view) {
+        view.setPointerCapture();
+    }
+
+    public static boolean hasPointerCapture(View view) {
+        return view.hasPointerCapture();
+    }
+
+    public static void releasePointerCapture(View view) {
+        view.releasePointerCapture();
+    }
+
+    public static void setPointerIcon(View view, Object pointerIcon) {
+        view.setPointerIcon((PointerIcon)pointerIcon);
+    }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index 17461a1..f997154 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -30,8 +30,8 @@
 def api20SS        = createApiSourceset('api20',        'api20',         '20',      kitkatSS)
 def api21SS        = createApiSourceset('api21',        'api21',         '21',      api20SS)
 def api22SS        = createApiSourceset('api22',        'api22',         '22',      api21SS)
-def api23SS        = createApiSourceset('api23',        'api23',         'current', api22SS)
-
+def api23SS        = createApiSourceset('api23',        'api23',         '23',      api22SS)
+def api24SS        = createApiSourceset('api24',        'api24',         'current', api23SS)
 
 def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
     def sourceSet = sourceSets.create(name)
diff --git a/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java b/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java
new file mode 100644
index 0000000..3bb9397
--- /dev/null
+++ b/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java
@@ -0,0 +1,44 @@
+/*
+ * 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.content.res;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+import android.util.DisplayMetrics;
+
+class ConfigurationHelperDonut {
+
+    static int getScreenHeightDp(@NonNull Resources resources) {
+        final DisplayMetrics metrics = resources.getDisplayMetrics();
+        return (int) (metrics.heightPixels / metrics.density);
+    }
+
+    static int getScreenWidthDp(@NonNull Resources resources) {
+        final DisplayMetrics metrics = resources.getDisplayMetrics();
+        return (int) (metrics.widthPixels / metrics.density);
+    }
+
+    static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+        // Not perfect, but close enough
+        return Math.min(getScreenWidthDp(resources), getScreenHeightDp(resources));
+    }
+
+    static int getDensityDpi(@NonNull Resources resources) {
+        return resources.getDisplayMetrics().densityDpi;
+    }
+}
diff --git a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
index f1bd117..6cd185b 100644
--- a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
@@ -33,4 +33,8 @@
     public static boolean metaStateHasNoModifiers(int metaState) {
         return KeyEvent.metaStateHasNoModifiers(metaState);
     }
+
+    public static boolean isCtrlPressed(KeyEvent event) {
+        return event.isCtrlPressed();
+    }
 }
diff --git a/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.java b/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.java
new file mode 100644
index 0000000..62eba95
--- /dev/null
+++ b/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.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.v4.content.res;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+
+class ConfigurationHelperHoneycombMr2 {
+
+    static int getScreenHeightDp(@NonNull Resources resources) {
+        return resources.getConfiguration().screenHeightDp;
+    }
+
+    static int getScreenWidthDp(@NonNull Resources resources) {
+        return resources.getConfiguration().screenWidthDp;
+    }
+
+    static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+        return resources.getConfiguration().smallestScreenWidthDp;
+    }
+}
diff --git a/v4/ics/android/support/v4/net/DatagramSocketWrapper.java b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
new file mode 100644
index 0000000..d7646d9
--- /dev/null
+++ b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.net;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+
+/** {@hide} */
+class DatagramSocketWrapper extends Socket {
+    public DatagramSocketWrapper(DatagramSocket socket, FileDescriptor fd) throws SocketException {
+        super(new DatagramSocketImplWrapper(socket, fd));
+    }
+
+    /**
+     * Empty implementation which wires in the given {@link FileDescriptor}.
+     */
+    private static class DatagramSocketImplWrapper extends SocketImpl {
+        public DatagramSocketImplWrapper(DatagramSocket socket, FileDescriptor fd) {
+            super();
+            this.localport = socket.getLocalPort();
+            this.fd = fd;
+        }
+
+        @Override
+        protected void accept(SocketImpl newSocket) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected int available() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void bind(InetAddress address, int port) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void close() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void connect(String host, int port) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void connect(InetAddress address, int port) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void create(boolean isStreaming) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected InputStream getInputStream() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected OutputStream getOutputStream() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void listen(int backlog) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void connect(SocketAddress remoteAddr, int timeout) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        protected void sendUrgentData(int value) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Object getOption(int optID) throws SocketException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setOption(int optID, Object val) throws SocketException {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
index 0f63fce..e3f3fab 100644
--- a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
+++ b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
@@ -17,7 +17,9 @@
 package android.support.v4.net;
 
 import android.net.TrafficStats;
+import android.os.ParcelFileDescriptor;
 
+import java.net.DatagramSocket;
 import java.net.Socket;
 import java.net.SocketException;
 
@@ -52,4 +54,24 @@
     public static void untagSocket(Socket socket) throws SocketException {
         TrafficStats.untagSocket(socket);
     }
+
+    public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+        final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket);
+        TrafficStats.tagSocket(new DatagramSocketWrapper(socket, pfd.getFileDescriptor()));
+        // The developer is still using the FD, so we need to detach it to
+        // prevent the PFD finalizer from closing it in their face. We had to
+        // wait until after the tagging call above, since detaching clears out
+        // the getFileDescriptor() result which tagging depends on.
+        pfd.detachFd();
+    }
+
+    public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+        final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket);
+        TrafficStats.untagSocket(new DatagramSocketWrapper(socket, pfd.getFileDescriptor()));
+        // The developer is still using the FD, so we need to detach it to
+        // prevent the PFD finalizer from closing it in their face. We had to
+        // wait until after the tagging call above, since detaching clears out
+        // the getFileDescriptor() result which tagging depends on.
+        pfd.detachFd();
+    }
 }
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
similarity index 68%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v4/ics/android/support/v4/view/MotionEventCompatICS.java
index 8126b38..e7979de 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
+package android.support.v4.view;
 
-import android.app.Activity;
+import android.view.MotionEvent;
 
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+class MotionEventCompatICS {
+    public static int getButtonState(MotionEvent event) {
+        return event.getButtonState();
+    }
 }
diff --git a/v4/java/android/support/v4/app/ActivityCompat.java b/v4/java/android/support/v4/app/ActivityCompat.java
index 0926d49..4e699d4 100644
--- a/v4/java/android/support/v4/app/ActivityCompat.java
+++ b/v4/java/android/support/v4/app/ActivityCompat.java
@@ -300,6 +300,12 @@
      * When checking whether you have a permission you should use {@link
      * #checkSelfPermission(android.content.Context, String)}.
      * </p>
+     * <p>
+     * Calling this API for permissions already granted to your app would show UI
+     * to the user to decided whether the app can still hold these permissions. This
+     * can be useful if the way your app uses the data guarded by the permissions
+     * changes significantly.
+     * </p>
      *
      * @param activity The target activity.
      * @param permissions The requested permissions.
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index 9117bd3..bec151c 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -423,6 +423,10 @@
         }
 
         if (containerViewId != 0) {
+            if (containerViewId == View.NO_ID) {
+                throw new IllegalArgumentException("Can't add fragment "
+                        + fragment + " with tag " + tag + " to container view with no id");
+            }
             if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                 throw new IllegalStateException("Can't change container ID of fragment "
                         + fragment + ": was " + fragment.mFragmentId
@@ -616,6 +620,18 @@
     public int commitAllowingStateLoss() {
         return commitInternal(true);
     }
+
+    @Override
+    public void commitNow() {
+        disallowAddToBackStack();
+        mManager.execSingleAction(this, false);
+    }
+
+    @Override
+    public void commitNowAllowingStateLoss() {
+        disallowAddToBackStack();
+        mManager.execSingleAction(this, true);
+    }
     
     int commitInternal(boolean allowStateLoss) {
         if (mCommitted) throw new IllegalStateException("commit already called");
@@ -649,7 +665,7 @@
         TransitionState state = null;
         SparseArray<Fragment> firstOutFragments = null;
         SparseArray<Fragment> lastInFragments = null;
-        if (SUPPORTS_TRANSITIONS) {
+        if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
             firstOutFragments = new SparseArray<Fragment>();
             lastInFragments = new SparseArray<Fragment>();
 
@@ -770,6 +786,10 @@
                     firstOutFragments.remove(containerId);
                 }
             }
+            if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED) {
+                mManager.makeActive(fragment);
+                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+            }
         }
     }
 
@@ -890,7 +910,7 @@
             dump("  ", null, pw, null);
         }
 
-        if (SUPPORTS_TRANSITIONS) {
+        if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
             if (state == null) {
                 if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
                     state = beginTransition(firstOutFragments, lastInFragments, true);
@@ -1029,7 +1049,6 @@
      */
     private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
             SparseArray<Fragment> lastInFragments, boolean isBack) {
-        ensureFragmentsAreInitialized(lastInFragments);
         TransitionState state = new TransitionState();
 
         // Adding a non-existent target view makes sure that the transitions don't target
@@ -1064,21 +1083,6 @@
         return state;
     }
 
-    /**
-     * Ensure that fragments that are entering are at least at the CREATED state
-     * so that they may load Transitions using TransitionInflater.
-     */
-    private void ensureFragmentsAreInitialized(SparseArray<Fragment> lastInFragments) {
-        final int count = lastInFragments.size();
-        for (int i = 0; i < count; i++) {
-            final Fragment fragment = lastInFragments.valueAt(i);
-            if (fragment.mState < Fragment.CREATED) {
-                mManager.makeActive(fragment);
-                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
-            }
-        }
-    }
-
     private static Object getEnterTransition(Fragment inFragment, boolean isBack) {
         if (inFragment == null) {
             return null;
@@ -1187,7 +1191,8 @@
                     callback.onSharedElementStart(names, views, null);
                 }
                 prepareSharedElementTransition(state, sceneRoot, sharedElementTransition,
-                        inFragment, outFragment, isBack, sharedElementTargets);
+                        inFragment, outFragment, isBack, sharedElementTargets, enterTransition,
+                        exitTransition);
             }
         }
         if (enterTransition == null && sharedElementTransition == null &&
@@ -1234,9 +1239,9 @@
 
         if (transition != null) {
             FragmentTransitionCompat21.addTransitionTargets(enterTransition,
-                    sharedElementTransition, sceneRoot, viewRetriever, state.nonExistentView,
-                    state.enteringEpicenterView, state.nameOverrides, enteringViews,
-                    namedViews, renamedViews, sharedElementTargets);
+                    sharedElementTransition, exitTransition, sceneRoot, viewRetriever,
+                    state.nonExistentView, state.enteringEpicenterView, state.nameOverrides,
+                    enteringViews, exitingViews, namedViews, renamedViews, sharedElementTargets);
             excludeHiddenFragmentsAfterEnter(sceneRoot, state, containerId, transition);
 
             // We want to exclude hidden views later, so we need a non-null list in the
@@ -1258,16 +1263,22 @@
     private void prepareSharedElementTransition(final TransitionState state, final View sceneRoot,
             final Object sharedElementTransition, final Fragment inFragment,
             final Fragment outFragment, final boolean isBack,
-            final ArrayList<View> sharedElementTargets) {
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+            final ArrayList<View> sharedElementTargets, final Object enterTransition,
+            final Object exitTransition) {
+        if (sharedElementTransition != null) {
+            sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+                    new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
 
-                if (sharedElementTransition != null) {
+                    // Remove the exclude for the shared elements from the exiting fragment.
                     FragmentTransitionCompat21.removeTargets(sharedElementTransition,
                             sharedElementTargets);
+                    // keep the nonExistentView as excluded so the list doesn't get emptied
+                    sharedElementTargets.remove(state.nonExistentView);
+                    FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+                            exitTransition, sharedElementTransition, sharedElementTargets, false);
                     sharedElementTargets.clear();
 
                     ArrayMap<String, View> namedViews = mapSharedElementsIn(
@@ -1279,11 +1290,14 @@
 
                     callSharedElementEnd(state, inFragment, outFragment, isBack,
                             namedViews);
-                }
 
-                return true;
-            }
-        });
+                    // Exclude the shared elements from the entering fragment.
+                    FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+                            exitTransition, sharedElementTransition, sharedElementTargets, true);
+                    return true;
+                }
+            });
+        }
     }
 
     private void callSharedElementEnd(TransitionState state, Fragment inFragment,
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index af99b1c..635a3a9 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -59,11 +59,12 @@
     final boolean mRetainInstance;
     final boolean mDetached;
     final Bundle mArguments;
+    final boolean mHidden;
     
     Bundle mSavedFragmentState;
     
     Fragment mInstance;
-    
+
     public FragmentState(Fragment frag) {
         mClassName = frag.getClass().getName();
         mIndex = frag.mIndex;
@@ -74,6 +75,7 @@
         mRetainInstance = frag.mRetainInstance;
         mDetached = frag.mDetached;
         mArguments = frag.mArguments;
+        mHidden = frag.mHidden;
     }
     
     public FragmentState(Parcel in) {
@@ -86,38 +88,39 @@
         mRetainInstance = in.readInt() != 0;
         mDetached = in.readInt() != 0;
         mArguments = in.readBundle();
+        mHidden = in.readInt() != 0;
         mSavedFragmentState = in.readBundle();
     }
 
-    public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
-        if (mInstance != null) {
-            return mInstance;
+    public Fragment instantiate(FragmentHostCallback host, Fragment parent,
+            FragmentManagerNonConfig childNonConfig) {
+        if (mInstance == null) {
+            final Context context = host.getContext();
+            if (mArguments != null) {
+                mArguments.setClassLoader(context.getClassLoader());
+            }
+
+            mInstance = Fragment.instantiate(context, mClassName, mArguments);
+
+            if (mSavedFragmentState != null) {
+                mSavedFragmentState.setClassLoader(context.getClassLoader());
+                mInstance.mSavedFragmentState = mSavedFragmentState;
+            }
+            mInstance.setIndex(mIndex, parent);
+            mInstance.mFromLayout = mFromLayout;
+            mInstance.mRestored = true;
+            mInstance.mFragmentId = mFragmentId;
+            mInstance.mContainerId = mContainerId;
+            mInstance.mTag = mTag;
+            mInstance.mRetainInstance = mRetainInstance;
+            mInstance.mDetached = mDetached;
+            mInstance.mHidden = mHidden;
+            mInstance.mFragmentManager = host.mFragmentManager;
+
+            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+                    "Instantiated fragment " + mInstance);
         }
-
-        final Context context = host.getContext();
-        if (mArguments != null) {
-            mArguments.setClassLoader(context.getClassLoader());
-        }
-
-        mInstance = Fragment.instantiate(context, mClassName, mArguments);
-
-        if (mSavedFragmentState != null) {
-            mSavedFragmentState.setClassLoader(context.getClassLoader());
-            mInstance.mSavedFragmentState = mSavedFragmentState;
-        }
-        mInstance.setIndex(mIndex, parent);
-        mInstance.mFromLayout = mFromLayout;
-        mInstance.mRestored = true;
-        mInstance.mFragmentId = mFragmentId;
-        mInstance.mContainerId = mContainerId;
-        mInstance.mTag = mTag;
-        mInstance.mRetainInstance = mRetainInstance;
-        mInstance.mDetached = mDetached;
-        mInstance.mFragmentManager = host.mFragmentManager;
-
-        if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                "Instantiated fragment " + mInstance);
-
+        mInstance.mChildNonConfig = childNonConfig;
         return mInstance;
     }
 
@@ -135,6 +138,7 @@
         dest.writeInt(mRetainInstance ? 1 : 0);
         dest.writeInt(mDetached ? 1 : 0);
         dest.writeBundle(mArguments);
+        dest.writeInt(mHidden? 1 : 0);
         dest.writeBundle(mSavedFragmentState);
     }
     
@@ -240,6 +244,10 @@
     // Private fragment manager for child fragments inside of this one.
     FragmentManagerImpl mChildFragmentManager;
 
+    // For use when restoring fragment state and descendant fragments are retained.
+    // This state is set by FragmentState.instantiate and cleared in onCreate.
+    FragmentManagerNonConfig mChildNonConfig;
+
     // If this Fragment is contained in another Fragment, this is that container.
     Fragment mParentFragment;
 
@@ -813,10 +821,6 @@
      * </ul>
      */
     public void setRetainInstance(boolean retain) {
-        if (retain && mParentFragment != null) {
-            throw new IllegalStateException(
-                    "Can't retain fragements that are nested in other fragments");
-        }
         mRetainInstance = retain;
     }
     
@@ -872,7 +876,7 @@
      *                        false if it is not.
      */
     public void setUserVisibleHint(boolean isVisibleToUser) {
-        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
+        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null) {
             mFragmentManager.performPendingDeferredStart(this);
         }
         mUserVisibleHint = isVisibleToUser;
@@ -994,6 +998,12 @@
      * android.content.Context#checkSelfPermission(String)}.
      * </p>
      * <p>
+     * Calling this API for permissions already granted to your app would show UI
+     * to the user to decided whether the app can still hold these permissions. This
+     * can be useful if the way your app uses the data guarded by the permissions
+     * changes significantly.
+     * </p>
+     * <p>
      * A sample permissions request looks like this:
      * </p>
      * <code><pre><p>
@@ -1193,12 +1203,27 @@
      * on things like the activity's content view hierarchy being initialized
      * at this point.  If you want to do work once the activity itself is
      * created, see {@link #onActivityCreated(Bundle)}.
+     *
+     * <p>Any restored child fragments will be created before the base
+     * <code>Fragment.onCreate</code> method returns.</p>
      * 
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
     public void onCreate(@Nullable Bundle savedInstanceState) {
         mCalled = true;
+        if (savedInstanceState != null) {
+            Parcelable p = savedInstanceState.getParcelable(
+                    FragmentActivity.FRAGMENTS_TAG);
+            if (p != null) {
+                if (mChildFragmentManager == null) {
+                    instantiateChildFragmentManager();
+                }
+                mChildFragmentManager.restoreAllState(p, mChildNonConfig);
+                mChildNonConfig = null;
+                mChildFragmentManager.dispatchCreate();
+            }
+        }
     }
 
     /**
@@ -1953,17 +1978,6 @@
             throw new SuperNotCalledException("Fragment " + this
                     + " did not call through to super.onCreate()");
         }
-        if (savedInstanceState != null) {
-            Parcelable p = savedInstanceState.getParcelable(
-                    FragmentActivity.FRAGMENTS_TAG);
-            if (p != null) {
-                if (mChildFragmentManager == null) {
-                    instantiateChildFragmentManager();
-                }
-                mChildFragmentManager.restoreAllState(p, null);
-                mChildFragmentManager.dispatchCreate();
-            }
-        }
     }
 
     View performCreateView(LayoutInflater inflater, ViewGroup container,
@@ -2204,5 +2218,6 @@
             throw new SuperNotCalledException("Fragment " + this
                     + " did not call through to super.onDestroy()");
         }
+        mChildFragmentManager = null;
     }
 }
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 5bde380..c6e8b36 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -143,7 +143,7 @@
 
     static final class NonConfigurationInstances {
         Object custom;
-        List<Fragment> fragments;
+        FragmentManagerNonConfig fragments;
         SimpleArrayMap<String, LoaderManager> loaders;
     }
 
@@ -543,7 +543,7 @@
 
         Object custom = onRetainCustomNonConfigurationInstance();
 
-        List<Fragment> fragments = mFragments.retainNonConfig();
+        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
         SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
 
         if (fragments == null && loaders == null && custom == null) {
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 4a80fcc..60beed9 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -139,16 +139,41 @@
      * instances retained across configuration changes.
      *
      * @see #retainNonConfig()
+     *
+     * @deprecated use {@link #restoreAllState(Parcelable, FragmentManagerNonConfig)}
      */
     public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
-        mHost.mFragmentManager.restoreAllState(state, nonConfigList);
+        mHost.mFragmentManager.restoreAllState(state,
+                new FragmentManagerNonConfig(nonConfigList, null));
+    }
+
+    /**
+     * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
+     * instances retained across configuration changes, including nested fragments
+     *
+     * @see #retainNestedNonConfig()
+     */
+    public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
+        mHost.mFragmentManager.restoreAllState(state, nonConfig);
     }
 
     /**
      * Returns a list of Fragments that have opted to retain their instance across
      * configuration changes.
+     *
+     * @deprecated use {@link #retainNestedNonConfig()} to also track retained
+     *             nested child fragments
      */
     public List<Fragment> retainNonConfig() {
+        FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
+        return nonconf != null ? nonconf.getFragments() : null;
+    }
+
+    /**
+     * Returns a nested tree of Fragments that have opted to retain their instance across
+     * configuration changes.
+     */
+    public FragmentManagerNonConfig retainNestedNonConfig() {
         return mHost.mFragmentManager.retainNonConfig();
     }
 
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index b1b988e..4154da3 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Bundle;
@@ -162,6 +163,12 @@
      * all callbacks and other related behavior will be done from within this
      * call, so be careful about where this is called from.
      *
+     * <p>If you are committing a single transaction that does not modify the
+     * fragment back stack, strongly consider using
+     * {@link FragmentTransaction#commitNow()} instead. This can help avoid
+     * unwanted side effects when other code in your app has pending committed
+     * transactions that expect different timing.</p>
+     *
      * @return Returns true if there were any pending transactions to be
      * executed.
      */
@@ -1054,12 +1061,24 @@
                         if (!f.mFromLayout) {
                             ViewGroup container = null;
                             if (f.mContainerId != 0) {
-                                container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
+                                if (f.mContainerId == View.NO_ID) {
+                                    throwException(new IllegalArgumentException(
+                                            "Cannot create fragment "
+                                                    + f
+                                                    + " for a container view with no id"));
+                                }
+                                container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                                 if (container == null && !f.mRestored) {
+                                    String resName;
+                                    try {
+                                        resName = f.getResources().getResourceName(f.mContainerId);
+                                    } catch (NotFoundException e) {
+                                        resName = "unknown";
+                                    }
                                     throwException(new IllegalArgumentException(
                                             "No view found for id 0x"
                                             + Integer.toHexString(f.mContainerId) + " ("
-                                            + f.getResources().getResourceName(f.mContainerId)
+                                            + resName
                                             + ") for fragment " + f));
                                 }
                             }
@@ -1145,6 +1164,7 @@
                             }
                             if (anim != null) {
                                 final Fragment fragment = f;
+                                final ViewGroup container = f.mContainer;
                                 f.mAnimatingAway = f.mView;
                                 f.mStateAfterAnimating = newState;
                                 final View viewToAnimate = f.mView;
@@ -1154,6 +1174,7 @@
                                     public void onAnimationEnd(Animation animation) {
                                         super.onAnimationEnd(animation);
                                         if (fragment.mAnimatingAway != null) {
+                                            container.removeView(fragment.mAnimatingAway);
                                             fragment.mAnimatingAway = null;
                                             moveToState(fragment, fragment.mStateAfterAnimating,
                                                     0, 0, false);
@@ -1161,8 +1182,9 @@
                                     }
                                 });
                                 f.mView.startAnimation(anim);
+                            } else {
+                                f.mContainer.removeView(f.mView);
                             }
-                            f.mContainer.removeView(f.mView);
                         }
                         f.mContainer = null;
                         f.mView = null;
@@ -1581,16 +1603,36 @@
         }
     }
 
+    public void execSingleAction(Runnable action, boolean allowStateLoss) {
+        if (mExecutingActions) {
+            throw new IllegalStateException("FragmentManager is already executing transactions");
+        }
+
+        if (Looper.myLooper() != mHost.getHandler().getLooper()) {
+            throw new IllegalStateException("Must be called from main thread of fragment host");
+        }
+
+        if (!allowStateLoss) {
+            checkStateLoss();
+        }
+
+        mExecutingActions = true;
+        action.run();
+        mExecutingActions = false;
+
+        doPendingDeferredStart();
+    }
+
     /**
      * Only call from main thread!
      */
     public boolean execPendingActions() {
         if (mExecutingActions) {
-            throw new IllegalStateException("Recursive entry to executePendingTransactions");
+            throw new IllegalStateException("FragmentManager is already executing transactions");
         }
         
         if (Looper.myLooper() != mHost.getHandler().getLooper()) {
-            throw new IllegalStateException("Must be called from main thread of process");
+            throw new IllegalStateException("Must be called from main thread of fragment host");
         }
 
         boolean didSomething = false;
@@ -1621,9 +1663,15 @@
             didSomething = true;
         }
         
+        doPendingDeferredStart();
+
+        return didSomething;
+    }
+
+    void doPendingDeferredStart() {
         if (mHavePendingDeferredStart) {
             boolean loadersRunning = false;
-            for (int i=0; i<mActive.size(); i++) {
+            for (int i = 0; i < mActive.size(); i++) {
                 Fragment f = mActive.get(i);
                 if (f != null && f.mLoaderManager != null) {
                     loadersRunning |= f.mLoaderManager.hasRunningLoaders();
@@ -1634,7 +1682,6 @@
                 startPendingDeferredFragments();
             }
         }
-        return didSomething;
     }
 
     void reportBackStackChanged() {
@@ -1666,7 +1713,9 @@
             final BackStackRecord bss = mBackStack.remove(last);
             SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
             SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
-            bss.calculateBackFragments(firstOutFragments, lastInFragments);
+            if (mCurState >= Fragment.CREATED) {
+                bss.calculateBackFragments(firstOutFragments, lastInFragments);
+            }
             bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
             reportBackStackChanged();
         } else {
@@ -1713,8 +1762,10 @@
             final int LAST = states.size()-1;
             SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
             SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
-            for (int i=0; i<=LAST; i++) {
-                states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
+            if (mCurState >= Fragment.CREATED) {
+                for (int i = 0; i <= LAST; i++) {
+                    states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
+                }
             }
             BackStackRecord.TransitionState state = null;
             for (int i=0; i<=LAST; i++) {
@@ -1727,23 +1778,46 @@
         return true;
     }
     
-    ArrayList<Fragment> retainNonConfig() {
+    FragmentManagerNonConfig retainNonConfig() {
         ArrayList<Fragment> fragments = null;
+        ArrayList<FragmentManagerNonConfig> childFragments = null;
         if (mActive != null) {
             for (int i=0; i<mActive.size(); i++) {
                 Fragment f = mActive.get(i);
-                if (f != null && f.mRetainInstance) {
-                    if (fragments == null) {
-                        fragments = new ArrayList<Fragment>();
+                if (f != null) {
+                    if (f.mRetainInstance) {
+                        if (fragments == null) {
+                            fragments = new ArrayList<Fragment>();
+                        }
+                        fragments.add(f);
+                        f.mRetaining = true;
+                        f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
+                        if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
                     }
-                    fragments.add(f);
-                    f.mRetaining = true;
-                    f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
-                    if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
+                    boolean addedChild = false;
+                    if (f.mChildFragmentManager != null) {
+                        FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
+                        if (child != null) {
+                            if (childFragments == null) {
+                                childFragments = new ArrayList<FragmentManagerNonConfig>();
+                                for (int j = 0; j < i; j++) {
+                                    childFragments.add(null);
+                                }
+                            }
+                            childFragments.add(child);
+                            addedChild = true;
+                        }
+                    }
+                    if (childFragments != null && !addedChild) {
+                        childFragments.add(null);
+                    }
                 }
             }
         }
-        return fragments;
+        if (fragments == null && childFragments == null) {
+            return null;
+        }
+        return new FragmentManagerNonConfig(fragments, childFragments);
     }
     
     void saveFragmentViewState(Fragment f) {
@@ -1911,18 +1985,23 @@
         return fms;
     }
     
-    void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
+    void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
         // If there is no saved state at all, then there can not be
         // any nonConfig fragments either, so that is that.
         if (state == null) return;
         FragmentManagerState fms = (FragmentManagerState)state;
         if (fms.mActive == null) return;
-        
+
+        List<FragmentManagerNonConfig> childNonConfigs = null;
+
         // First re-attach any non-config instances we are retaining back
         // to their saved state, so we don't try to instantiate them again.
         if (nonConfig != null) {
-            for (int i=0; i<nonConfig.size(); i++) {
-                Fragment f = nonConfig.get(i);
+            List<Fragment> nonConfigFragments = nonConfig.getFragments();
+            childNonConfigs = nonConfig.getChildNonConfigs();
+            final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+            for (int i = 0; i < count; i++) {
+                Fragment f = nonConfigFragments.get(i);
                 if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
                 FragmentState fs = fms.mActive[f.mIndex];
                 fs.mInstance = f;
@@ -1942,14 +2021,18 @@
         
         // Build the full list of active fragments, instantiating them from
         // their saved state.
-        mActive = new ArrayList<Fragment>(fms.mActive.length);
+        mActive = new ArrayList<>(fms.mActive.length);
         if (mAvailIndices != null) {
             mAvailIndices.clear();
         }
         for (int i=0; i<fms.mActive.length; i++) {
             FragmentState fs = fms.mActive[i];
             if (fs != null) {
-                Fragment f = fs.instantiate(mHost, mParent);
+                FragmentManagerNonConfig childNonConfig = null;
+                if (childNonConfigs != null && i < childNonConfigs.size()) {
+                    childNonConfig = childNonConfigs.get(i);
+                }
+                Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
                 if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                 mActive.add(f);
                 // Now that the fragment is instantiated (or came from being
@@ -1968,8 +2051,10 @@
         
         // Update the target of all retained fragments.
         if (nonConfig != null) {
-            for (int i=0; i<nonConfig.size(); i++) {
-                Fragment f = nonConfig.get(i);
+            List<Fragment> nonConfigFragments = nonConfig.getFragments();
+            final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+            for (int i = 0; i < count; i++) {
+                Fragment f = nonConfigFragments.get(i);
                 if (f.mTargetIndex >= 0) {
                     if (f.mTargetIndex < mActive.size()) {
                         f.mTarget = mActive.get(f.mTargetIndex);
diff --git a/v4/java/android/support/v4/app/FragmentManagerNonConfig.java b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
new file mode 100644
index 0000000..832426a
--- /dev/null
+++ b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
@@ -0,0 +1,56 @@
+/*
+ * 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.app;
+
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * FragmentManagerNonConfig stores the retained instance fragments across
+ * activity recreation events.
+ *
+ * <p>Apps should treat objects of this type as opaque, returned by
+ * and passed to the state save and restore process for fragments in
+ * {@link FragmentController#retainNonConfig()} and
+ * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ */
+public class FragmentManagerNonConfig {
+    private final List<Fragment> mFragments;
+    private final List<FragmentManagerNonConfig> mChildNonConfigs;
+
+    FragmentManagerNonConfig(List<Fragment> fragments,
+            List<FragmentManagerNonConfig> childNonConfigs) {
+        mFragments = fragments;
+        mChildNonConfigs = childNonConfigs;
+    }
+
+    /**
+     * @return the retained instance fragments returned by a FragmentManager
+     */
+    List<Fragment> getFragments() {
+        return mFragments;
+    }
+
+    /**
+     * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
+     */
+    List<FragmentManagerNonConfig> getChildNonConfigs() {
+        return mChildNonConfigs;
+    }
+}
diff --git a/v4/java/android/support/v4/app/FragmentPagerAdapter.java b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
index 01ca411..d0d1006 100644
--- a/v4/java/android/support/v4/app/FragmentPagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
@@ -77,6 +77,10 @@
 
     @Override
     public void startUpdate(ViewGroup container) {
+        if (container.getId() == View.NO_ID) {
+            throw new IllegalStateException("ViewPager with adapter " + this
+                    + " requires a view id");
+        }
     }
 
     @Override
@@ -136,9 +140,8 @@
     @Override
     public void finishUpdate(ViewGroup container) {
         if (mCurTransaction != null) {
-            mCurTransaction.commitAllowingStateLoss();
+            mCurTransaction.commitNowAllowingStateLoss();
             mCurTransaction = null;
-            mFragmentManager.executePendingTransactions();
         }
     }
 
diff --git a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
index ad9072b..0154750 100644
--- a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -83,6 +83,10 @@
 
     @Override
     public void startUpdate(ViewGroup container) {
+        if (container.getId() == View.NO_ID) {
+            throw new IllegalStateException("ViewPager with adapter " + this
+                    + " requires a view id");
+        }
     }
 
     @Override
@@ -159,9 +163,8 @@
     @Override
     public void finishUpdate(ViewGroup container) {
         if (mCurTransaction != null) {
-            mCurTransaction.commitAllowingStateLoss();
+            mCurTransaction.commitNowAllowingStateLoss();
             mCurTransaction = null;
-            mFragmentManager.executePendingTransactions();
         }
     }
 
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 48f2088..4dbc791 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -162,7 +162,7 @@
     public static final int TRANSIT_EXIT_MASK = 0x2000;
 
     /** @hide */
-    @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE})
+    @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
     @Retention(RetentionPolicy.SOURCE)
     private @interface Transit {}
 
@@ -212,7 +212,7 @@
     /**
      * Select a standard transition animation for this transaction.  May be
      * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
-     * or {@link #TRANSIT_FRAGMENT_CLOSE}
+     * {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
      */
     public abstract FragmentTransaction setTransition(@Transit int transit);
 
@@ -303,4 +303,45 @@
      * to change unexpectedly on the user.
      */
     public abstract int commitAllowingStateLoss();
+
+    /**
+     * Commits this transaction synchronously. Any added fragments will be
+     * initialized and brought completely to the lifecycle state of their host
+     * and any removed fragments will be torn down accordingly before this
+     * call returns. Committing a transaction in this way allows fragments
+     * to be added as dedicated, encapsulated components that monitor the
+     * lifecycle state of their host while providing firmer ordering guarantees
+     * around when those fragments are fully initialized and ready. Fragments
+     * that manage views will have those views created and attached.
+     *
+     * <p>Calling <code>commitNow</code> is preferable to calling
+     * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
+     * as the latter will have the side effect of attempting to commit <em>all</em>
+     * currently pending transactions whether that is the desired behavior
+     * or not.</p>
+     *
+     * <p>Transactions committed in this way may not be added to the
+     * FragmentManager's back stack, as doing so would break other expected
+     * ordering guarantees for other asynchronously committed transactions.
+     * This method will throw {@link IllegalStateException} if the transaction
+     * previously requested to be added to the back stack with
+     * {@link #addToBackStack(String)}.</p>
+     *
+     * <p class="note">A transaction can only be committed with this method
+     * prior to its containing activity saving its state.  If the commit is
+     * attempted after that point, an exception will be thrown.  This is
+     * because the state after the commit can be lost if the activity needs to
+     * be restored from its state.  See {@link #commitAllowingStateLoss()} for
+     * situations where it may be okay to lose the commit.</p>
+     */
+    public abstract void commitNow();
+
+    /**
+     * Like {@link #commitNow} but allows the commit to be executed after an
+     * activity's state is saved.  This is dangerous because the commit can
+     * be lost if the activity needs to later be restored from its state, so
+     * this should only be used for cases where it is okay for the UI state
+     * to change unexpectedly on the user.
+     */
+    public abstract void commitNowAllowingStateLoss();
 }
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index 35e7d30..e6cbd79 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -3020,7 +3020,7 @@
         /**
          * Gets the accent color.
          *
-         * @see setColor
+         * @see #setColor
          */
         @ColorInt
         public int getColor() {
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 16ec1da..efb1e38 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -26,6 +26,7 @@
 import android.os.Environment;
 import android.os.Process;
 import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.os.EnvironmentCompat;
 import android.util.Log;
 
@@ -114,6 +115,30 @@
     }
 
     /**
+     * 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()},
+     * {@link Context#getCacheDir()}, {@link Context#getDir(String, int)}, or
+     * other storage APIs on {@link Context}.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
+     *
+     * @see ApplicationInfo#dataDir
+     */
+    public static File getDataDir(Context context) {
+        if (BuildCompat.isAtLeastN()) {
+            return ContextCompatApi24.getDataDir(context);
+        } else {
+            final String dataDir = context.getApplicationInfo().dataDir;
+            return dataDir != null ? new File(dataDir) : null;
+        }
+    }
+
+    /**
      * Returns absolute paths to application-specific directories on all
      * external storage devices where the application's OBB files (if there are
      * any) can be found. Note if the application does not have any OBB files,
@@ -451,4 +476,69 @@
         }
         return file;
     }
+
+    /**
+     * Return a new Context object for the current Context but whose storage
+     * APIs are backed by device-protected storage.
+     * <p>
+     * On devices with direct boot, data stored in this location is encrypted
+     * with a key tied to the physical device, and it can be accessed
+     * immediately after the device has booted successfully, both
+     * <em>before and after</em> the user has authenticated with their
+     * credentials (such as a lock pattern or PIN).
+     * <p>
+     * Because device-protected data is available without user authentication,
+     * you should carefully limit the data you store using this Context. For
+     * example, storing sensitive authentication tokens or passwords in the
+     * device-protected area is strongly discouraged.
+     * <p>
+     * If the underlying device does not have the ability to store
+     * device-protected and credential-protected data using different keys, then
+     * both storage areas will become available at the same time. They remain as
+     * two distinct storage locations on disk, and only the window of
+     * availability changes.
+     * <p>
+     * Each call to this method returns a new instance of a Context object;
+     * Context objects are not shared, however common state (ClassLoader, other
+     * Resources for the same configuration) may be so the Context itself can be
+     * fairly lightweight.
+     * <p>
+     * Prior to {@link BuildCompat#isAtLeastN()} this method returns
+     * {@code null}, since device-protected storage is not available.
+     *
+     * @see ContextCompat#isDeviceProtectedStorage(Context)
+     */
+    public static Context createDeviceProtectedStorageContext(Context context) {
+        if (BuildCompat.isAtLeastN()) {
+            return ContextCompatApi24.createDeviceProtectedStorageContext(context);
+        } else {
+            return null;
+        }
+    }
+
+    /** @removed */
+    @Deprecated
+    public static Context createDeviceEncryptedStorageContext(Context context) {
+        return createDeviceProtectedStorageContext(context);
+    }
+
+    /**
+     * Indicates if the storage APIs of this Context are backed by
+     * device-encrypted storage.
+     *
+     * @see ContextCompat#createDeviceProtectedStorageContext(Context)
+     */
+    public static boolean isDeviceProtectedStorage(Context context) {
+        if (BuildCompat.isAtLeastN()) {
+            return ContextCompatApi24.isDeviceProtectedStorage(context);
+        } else {
+            return false;
+        }
+    }
+
+    /** @removed */
+    @Deprecated
+    public static boolean isDeviceEncryptedStorage(Context context) {
+        return isDeviceProtectedStorage(context);
+    }
 }
diff --git a/v4/java/android/support/v4/content/FileProvider.java b/v4/java/android/support/v4/content/FileProvider.java
index 515ce1c..9d6d803 100644
--- a/v4/java/android/support/v4/content/FileProvider.java
+++ b/v4/java/android/support/v4/content/FileProvider.java
@@ -365,7 +365,7 @@
      *
      * @param context A {@link Context} for the current component.
      * @param authority The authority of a {@link FileProvider} defined in a
-     *            {@code &lt;provider&gt;} element in your app's manifest.
+     *            {@code <provider>} element in your app's manifest.
      * @param file A {@link File} pointing to the filename for which you want a
      * <code>content</code> {@link Uri}.
      * @return A content URI for the file.
@@ -547,7 +547,7 @@
 
     /**
      * Parse and return {@link PathStrategy} for given authority as defined in
-     * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code &lt;meta-data>}.
+     * {@link #META_DATA_FILE_PROVIDER_PATHS} {@code <meta-data>}.
      *
      * @see #getPathStrategy(Context, String)
      */
diff --git a/v4/java/android/support/v4/content/res/ConfigurationHelper.java b/v4/java/android/support/v4/content/res/ConfigurationHelper.java
new file mode 100644
index 0000000..2ee20d0
--- /dev/null
+++ b/v4/java/android/support/v4/content/res/ConfigurationHelper.java
@@ -0,0 +1,136 @@
+/*
+ * 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.content.res;
+
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+/**
+ * Helper class which allows access to properties of {@link android.content.res.Configuration} in
+ * a backward compatible fashion.
+ */
+public final class ConfigurationHelper {
+
+    private static final ConfigurationHelperImpl IMPL;
+
+    static {
+        final int sdk = Build.VERSION.SDK_INT;
+        if (sdk >= 17) {
+            IMPL = new JellybeanMr1Impl();
+        } else if (sdk >= 13) {
+            IMPL = new HoneycombMr2Impl();
+        } else {
+            IMPL = new DonutImpl();
+        }
+    }
+
+    private ConfigurationHelper() {}
+
+    private interface ConfigurationHelperImpl {
+        int getScreenHeightDp(@NonNull Resources resources);
+        int getScreenWidthDp(@NonNull Resources resources);
+        int getSmallestScreenWidthDp(@NonNull Resources resources);
+        int getDensityDpi(@NonNull Resources resources);
+    }
+
+    private static class DonutImpl implements ConfigurationHelperImpl {
+        @Override
+        public int getScreenHeightDp(@NonNull Resources resources) {
+            return ConfigurationHelperDonut.getScreenHeightDp(resources);
+        }
+
+        @Override
+        public int getScreenWidthDp(@NonNull Resources resources) {
+            return ConfigurationHelperDonut.getScreenWidthDp(resources);
+        }
+
+        @Override
+        public int getSmallestScreenWidthDp(@NonNull Resources resources) {
+            return ConfigurationHelperDonut.getSmallestScreenWidthDp(resources);
+        }
+
+        @Override
+        public int getDensityDpi(@NonNull Resources resources) {
+            return ConfigurationHelperDonut.getDensityDpi(resources);
+        }
+    }
+
+    private static class HoneycombMr2Impl extends DonutImpl {
+        @Override
+        public int getScreenHeightDp(@NonNull Resources resources) {
+            return ConfigurationHelperHoneycombMr2.getScreenHeightDp(resources);
+        }
+
+        @Override
+        public int getScreenWidthDp(@NonNull Resources resources) {
+            return ConfigurationHelperHoneycombMr2.getScreenWidthDp(resources);
+        }
+
+        @Override
+        public int getSmallestScreenWidthDp(@NonNull Resources resources) {
+            return ConfigurationHelperHoneycombMr2.getSmallestScreenWidthDp(resources);
+        }
+    }
+
+    private static class JellybeanMr1Impl extends HoneycombMr2Impl {
+        @Override
+        public int getDensityDpi(@NonNull Resources resources) {
+            return ConfigurationHelperJellybeanMr1.getDensityDpi(resources);
+        }
+    }
+
+    /**
+     * Returns the current height of the available screen space, in dp units.
+     *
+     * <p>Uses {@code Configuration.screenHeightDp} when available, otherwise an approximation
+     * is computed and returned.</p>
+     */
+    public static int getScreenHeightDp(@NonNull Resources resources) {
+        return IMPL.getScreenHeightDp(resources);
+    }
+
+    /**
+     * Returns the current width of the available screen space, in dp units.
+     *
+     * <p>Uses {@code Configuration.screenWidthDp} when available, otherwise an approximation
+     * is computed and returned.</p>
+     */
+    public static int getScreenWidthDp(@NonNull Resources resources) {
+        return IMPL.getScreenWidthDp(resources);
+    }
+
+    /**
+     * Returns The smallest screen size an application will see in normal operation, in dp units.
+     *
+     * <p>Uses {@code Configuration.smallestScreenWidthDp} when available, otherwise an
+     * approximation is computed and returned.</p>
+     */
+    public static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+        return IMPL.getSmallestScreenWidthDp(resources);
+    }
+
+    /**
+     * Returns the target screen density being rendered to.
+     *
+     * <p>Uses {@code Configuration.densityDpi} when available, otherwise an approximation
+     * is computed and returned.</p>
+     */
+    public static int getDensityDpi(@NonNull Resources resources) {
+        return IMPL.getDensityDpi(resources);
+    }
+}
diff --git a/v4/java/android/support/v4/content/res/TypedArrayUtils.java b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
index 79e4ac8..0cc5885 100644
--- a/v4/java/android/support/v4/content/res/TypedArrayUtils.java
+++ b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
@@ -15,10 +15,12 @@
  */
 package android.support.v4.content.res;
 
+import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.AnyRes;
 import android.support.annotation.StyleableRes;
+import android.util.TypedValue;
 
 /**
  * Compat methods for accessing TypedArray values.
@@ -70,4 +72,13 @@
         }
         return val;
     }
+
+    public static int getAttr(Context context, int attr, int fallbackAttr) {
+        TypedValue value = new TypedValue();
+        context.getTheme().resolveAttribute(attr, value, true);
+        if (value.resourceId != 0) {
+            return attr;
+        }
+        return fallbackAttr;
+    }
 }
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index e4b309a..02e9a6d 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -89,12 +90,17 @@
      * to the media browse service when connecting and retrieving the root id
      * for browsing, or null if none. The contents of this bundle may affect
      * the information returned when browsing.
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
+     * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
      */
     public MediaBrowserCompat(Context context, ComponentName serviceComponent,
             ConnectionCallback callback, Bundle rootHints) {
-        if (android.os.Build.VERSION.SDK_INT >= 23) {
+        if (Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaBrowserImplApi24(context, serviceComponent, callback, rootHints);
+        } else if (Build.VERSION.SDK_INT >= 23) {
             mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
-        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+        } else if (Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
         } else {
             mImpl = new MediaBrowserServiceImplBase(context, serviceComponent, callback, rootHints);
@@ -169,7 +175,7 @@
      *
      * @throws IllegalStateException if not connected.
      */
-     public @NonNull MediaSessionCompat.Token getSessionToken() {
+    public @NonNull MediaSessionCompat.Token getSessionToken() {
         return mImpl.getSessionToken();
     }
 
@@ -402,7 +408,7 @@
         private ConnectionCallbackInternal mConnectionCallbackInternal;
 
         public ConnectionCallback() {
-            if (android.os.Build.VERSION.SDK_INT >= 21) {
+            if (Build.VERSION.SDK_INT >= 21) {
                 mConnectionCallbackObj =
                         MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
             } else {
@@ -533,8 +539,13 @@
         public SubscriptionCallbackApi21(SubscriptionCallback callback, Bundle options) {
             mSubscriptionCallback = callback;
             mOptions = options;
-            mSubscriptionCallbackObj =
-                    MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+            if (Build.VERSION.SDK_INT >= 24) {
+                mSubscriptionCallbackObj =
+                        MediaBrowserCompatApi24.createSubscriptionCallback(new StubApi24());
+            } else  {
+                mSubscriptionCallbackObj =
+                        MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+            }
         }
 
         /**
@@ -595,16 +606,7 @@
         private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
             @Override
             public void onChildrenLoaded(@NonNull String parentId, List<Parcel> children) {
-                List<MediaBrowserCompat.MediaItem> mediaItems = null;
-                if (children != null) {
-                    mediaItems = new ArrayList<>();
-                    for (Parcel parcel : children) {
-                        parcel.setDataPosition(0);
-                        mediaItems.add(
-                                MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
-                        parcel.recycle();
-                    }
-                }
+                List<MediaBrowserCompat.MediaItem> mediaItems = parcelListToItemList(children);
                 if (mOptions != null) {
                     SubscriptionCallbackApi21.this.onChildrenLoaded(parentId,
                             MediaBrowserCompatUtils.applyOptions(mediaItems, mOptions),
@@ -622,6 +624,35 @@
                     SubscriptionCallbackApi21.this.onError(parentId);
                 }
             }
+
+            List<MediaBrowserCompat.MediaItem> parcelListToItemList(
+                    List<Parcel> parcelList) {
+                if (parcelList == null) {
+                    return null;
+                }
+                List<MediaBrowserCompat.MediaItem> items = new ArrayList<>();
+                for (Parcel parcel : parcelList) {
+                    parcel.setDataPosition(0);
+                    items.add(MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
+                    parcel.recycle();
+                }
+                return items;
+            }
+        }
+
+        private class StubApi24 extends StubApi21
+                implements MediaBrowserCompatApi24.SubscriptionCallback {
+            @Override
+            public void onChildrenLoaded(@NonNull String parentId, @NonNull List<Parcel> children,
+                    @NonNull Bundle options) {
+                SubscriptionCallbackApi21.this.onChildrenLoaded(
+                        parentId, parcelListToItemList(children), mOptions);
+            }
+
+            @Override
+            public void onError(@NonNull String parentId, @NonNull Bundle options) {
+                SubscriptionCallbackApi21.this.onError(parentId, mOptions);
+            }
         }
     }
 
@@ -632,7 +663,7 @@
         final Object mItemCallbackObj;
 
         public ItemCallback() {
-            if (android.os.Build.VERSION.SDK_INT >= 23) {
+            if (Build.VERSION.SDK_INT >= 23) {
                 mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
             } else {
                 mItemCallbackObj = null;
@@ -889,7 +920,7 @@
                 sub = new Subscription();
                 mSubscriptions.put(parentId, sub);
             }
-            sub.setCallbackForOptions(callback, options);
+            sub.putCallback(options, callback);
 
             // If we are connected, tell the service that we are watching. If we aren't
             // connected, the service will be told when we connect.
@@ -914,7 +945,7 @@
             Subscription sub = mSubscriptions.get(parentId);
 
             // Tell the service if necessary.
-            if (sub != null && sub.remove(options) && mState == CONNECT_STATE_CONNECTED) {
+            if (sub != null && sub.removeCallback(options) && mState == CONNECT_STATE_CONNECTED) {
                 try {
                     mServiceBinderWrapper.removeSubscription(
                             parentId, options, mCallbacksMessenger);
@@ -1284,7 +1315,7 @@
                 sub = new Subscription();
                 mSubscriptions.put(parentId, sub);
             }
-            sub.setCallbackForOptions(cb21, options);
+            sub.putCallback(options, cb21);
             if (MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
                 if (options == null || mServiceBinderWrapper == null) {
                     MediaBrowserCompatApi21.subscribe(
@@ -1311,7 +1342,7 @@
 
             // Remove from our list.
             Subscription sub = mSubscriptions.get(parentId);
-            if (sub != null && sub.remove(options)) {
+            if (sub != null && sub.removeCallback(options)) {
                 // Tell the service if necessary.
                 if (options == null || mServiceBinderWrapper == null) {
                     if (mServiceBinderWrapper != null || sub.isEmpty()) {
@@ -1480,6 +1511,35 @@
         }
     }
 
+    static class MediaBrowserImplApi24 extends MediaBrowserImplApi23 {
+        public MediaBrowserImplApi24(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            super(context, serviceComponent, callback, rootHints);
+        }
+
+        @Override
+        public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+                @NonNull SubscriptionCallback callback) {
+            SubscriptionCallbackApi21 cb21 = new SubscriptionCallbackApi21(callback, options);
+            if (options == null) {
+                MediaBrowserCompatApi21.subscribe(
+                        mBrowserObj, parentId, cb21.mSubscriptionCallbackObj);
+            } else {
+                MediaBrowserCompatApi24.subscribe(
+                        mBrowserObj, parentId, options, cb21.mSubscriptionCallbackObj);
+            }
+        }
+
+        @Override
+        public void unsubscribe(@NonNull String parentId, Bundle options) {
+            if (options == null) {
+                MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+            } else {
+                MediaBrowserCompatApi24.unsubscribe(mBrowserObj, parentId, options);
+            }
+        }
+    }
+
     private static class Subscription {
         private final List<SubscriptionCallback> mCallbacks;
         private final List<Bundle> mOptionsList;
@@ -1501,7 +1561,16 @@
             return mCallbacks;
         }
 
-        public void setCallbackForOptions(SubscriptionCallback callback, Bundle options) {
+        public SubscriptionCallback getCallback(Bundle options) {
+            for (int i = 0; i < mOptionsList.size(); ++i) {
+                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
+                    return mCallbacks.get(i);
+                }
+            }
+            return null;
+        }
+
+        public void putCallback(Bundle options, SubscriptionCallback callback) {
             for (int i = 0; i < mOptionsList.size(); ++i) {
                 if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
                     mCallbacks.set(i, callback);
@@ -1512,7 +1581,7 @@
             mOptionsList.add(options);
         }
 
-        public boolean remove(Bundle options) {
+        public boolean removeCallback(Bundle options) {
             for (int i = 0; i < mOptionsList.size(); ++i) {
                 if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
                     mCallbacks.remove(i);
@@ -1522,15 +1591,6 @@
             }
             return false;
         }
-
-        public SubscriptionCallback getCallback(Bundle options) {
-            for (int i = 0; i < mOptionsList.size(); ++i) {
-                if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
-                    return mCallbacks.get(i);
-                }
-            }
-            return null;
-        }
     }
 
     private static class CallbackHandler extends Handler {
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index e9d6311..8122980 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -151,6 +151,21 @@
         }
     }
 
+    class MediaBrowserServiceImplApi24 implements MediaBrowserServiceImpl {
+        private Object mServiceObj;
+
+        @Override
+        public void onCreate() {
+            mServiceObj = MediaBrowserServiceCompatApi24.createService();
+            MediaBrowserServiceCompatApi24.onCreate(mServiceObj, new ServiceImplApi24());
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return MediaBrowserServiceCompatApi23.onBind(mServiceObj, intent);
+        }
+    }
+
     private final class ServiceHandler extends Handler {
         private final ServiceImpl mServiceImpl = new ServiceImpl();
 
@@ -435,27 +450,27 @@
         }
 
         @Override
-        public void connect(final String pkg, final Bundle rootHints,
-                final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void connect(String pkg, Bundle rootHints,
+                MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
                     new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
-        public void disconnect(final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void disconnect(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
         }
 
 
         @Override
         public void addSubscription(
-                final String id, final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+                String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.addSubscription(id, null, new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
-        public void removeSubscription(final String id,
-                final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void removeSubscription(
+                String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.removeSubscription(id, null, new ServiceCallbacksApi21(callbacks));
         }
     }
@@ -463,7 +478,7 @@
     private class ServiceImplApi23 extends ServiceImplApi21
             implements MediaBrowserServiceCompatApi23.ServiceImplApi23 {
         @Override
-        public void getMediaItem(final String mediaId,
+        public void getMediaItem(String mediaId,
                 final MediaBrowserServiceCompatApi23.ItemCallback cb) {
             ResultReceiver receiverCompat = new ResultReceiver(mHandler) {
                 @Override
@@ -481,6 +496,27 @@
         }
     }
 
+    private class ServiceImplApi24 extends ServiceImplApi23
+            implements MediaBrowserServiceCompatApi24.ServiceImplApi24 {
+        @Override
+        public void connect(String pkg, Bundle rootHints,
+                MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+            mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
+                    new ServiceCallbacksApi24(callbacks));
+        }
+
+        @Override
+        public void addSubscription(String id, Bundle options,
+                MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+            mServiceImpl.addSubscription(id, options, new ServiceCallbacksApi24(callbacks));
+        }
+
+        public void removeSubscription(String id, Bundle options,
+                MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+            mServiceImpl.removeSubscription(id, options, new ServiceCallbacksApi24(callbacks));
+        }
+    }
+
     private interface ServiceCallbacks {
         IBinder asBinder();
         void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
@@ -540,10 +576,10 @@
     }
 
     private class ServiceCallbacksApi21 implements ServiceCallbacks {
-        final MediaBrowserServiceCompatApi21.ServiceCallbacks mCallbacks;
+        final MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 mCallbacks;
         Messenger mMessenger;
 
-        ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mCallbacks = callbacks;
         }
 
@@ -581,10 +617,39 @@
         }
     }
 
+    private class ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
+        final MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 mCallbacks;
+
+        ServiceCallbacksApi24(MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+            super(callbacks);
+            mCallbacks = callbacks;
+        }
+
+        public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list,
+                Bundle options) throws RemoteException {
+            List<Parcel> parcelList = null;
+            if (list != null) {
+                parcelList = new ArrayList<>();
+                for (MediaBrowserCompat.MediaItem item : list) {
+                    Parcel parcel = Parcel.obtain();
+                    item.writeToParcel(parcel, 0);
+                    parcelList.add(parcel);
+                }
+            }
+            if (options == null) {
+                mCallbacks.onLoadChildren(mediaId, parcelList);
+            } else {
+                mCallbacks.onLoadChildren(mediaId, parcelList, options);
+            }
+        }
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
-        if (Build.VERSION.SDK_INT >= 23) {
+        if (Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaBrowserServiceImplApi24();
+        } else if (Build.VERSION.SDK_INT >= 23) {
             mImpl = new MediaBrowserServiceImplApi23();
         } else if (Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserServiceImplApi21();
@@ -621,6 +686,9 @@
      *            root id for browsing, or null if none. The contents of this
      *            bundle may affect the information returned when browsing.
      * @return The {@link BrowserRoot} for accessing this app's content or null.
+     * @see BrowserRoot#EXTRA_RECENT
+     * @see BrowserRoot#EXTRA_OFFLINE
+     * @see BrowserRoot#EXTRA_SUGGESTED
      */
     public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
             int clientUid, @Nullable Bundle rootHints);
@@ -910,13 +978,13 @@
     private void performLoadItem(String itemId, final ResultReceiver receiver) {
         final Result<MediaBrowserCompat.MediaItem> result =
                 new Result<MediaBrowserCompat.MediaItem>(itemId) {
-            @Override
-            void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flag) {
-                Bundle bundle = new Bundle();
-                bundle.putParcelable(KEY_MEDIA_ITEM, item);
-                receiver.send(0, bundle);
-            }
-        };
+                    @Override
+                    void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flag) {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(KEY_MEDIA_ITEM, item);
+                        receiver.send(0, bundle);
+                    }
+                };
 
         MediaBrowserServiceCompat.this.onLoadItem(itemId, result);
 
@@ -944,7 +1012,6 @@
          *
          * @see #EXTRA_OFFLINE
          * @see #EXTRA_SUGGESTED
-         * @hide
          */
         public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
 
@@ -962,7 +1029,6 @@
          *
          * @see #EXTRA_RECENT
          * @see #EXTRA_SUGGESTED
-         * @hide
          */
         public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
 
@@ -972,7 +1038,8 @@
          *
          * <p>When creating a media browser for a given media browser service, this key can be
          * supplied as a root hint for retrieving the media items suggested by the media browser
-         * service.
+         * service. The list of media items passed in {@link android.support.v4.media.MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded(String, List)}
+         * is considered ordered by relevance, first being the top suggestion.
          * If the media browser service can provide such media items, the implementation must return
          * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
          *
@@ -980,7 +1047,6 @@
          *
          * @see #EXTRA_RECENT
          * @see #EXTRA_OFFLINE
-         * @hide
          */
         public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
 
diff --git a/v4/java/android/support/v4/media/session/IMediaSession.aidl b/v4/java/android/support/v4/media/session/IMediaSession.aidl
index 305dd0b..718b870 100644
--- a/v4/java/android/support/v4/media/session/IMediaSession.aidl
+++ b/v4/java/android/support/v4/media/session/IMediaSession.aidl
@@ -67,4 +67,8 @@
     CharSequence getQueueTitle();
     Bundle getExtras();
     int getRatingType();
+    void prepare();
+    void prepareFromMediaId(String uri, in Bundle extras);
+    void prepareFromSearch(String string, in Bundle extras);
+    void prepareFromUri(in Uri uri, in Bundle extras);
 }
diff --git a/v4/java/android/support/v4/media/session/MediaControllerCompat.java b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
index 998a24d..0c04c73 100644
--- a/v4/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -69,7 +69,9 @@
         }
         mToken = session.getSessionToken();
 
-        if (android.os.Build.VERSION.SDK_INT >= 23) {
+        if (android.os.Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaControllerImplApi24(context, session);
+        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
             mImpl = new MediaControllerImplApi23(context, session);
         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaControllerImplApi21(context, session);
@@ -92,7 +94,9 @@
         }
         mToken = sessionToken;
 
-        if (android.os.Build.VERSION.SDK_INT >= 23) {
+        if (android.os.Build.VERSION.SDK_INT >= 24) {
+            mImpl = new MediaControllerImplApi24(context, sessionToken);
+        } else if (android.os.Build.VERSION.SDK_INT >= 23) {
             mImpl = new MediaControllerImplApi23(context, sessionToken);
         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaControllerImplApi21(context, sessionToken);
@@ -573,6 +577,62 @@
         }
 
         /**
+         * Request that the player prepare its playback without audio focus. In other words, other
+         * session can continue to play during the preparation of this session. This method can be
+         * used to speed up the start of the playback. Once the preparation is done, the session
+         * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards,
+         * {@link #play} can be called to start playback. If the preparation is not needed,
+         * {@link #play} can be directly called without this method.
+         */
+        public abstract void prepare();
+
+        /**
+         * Request that the player prepare playback for a specific media id. In other words, other
+         * session can continue to play during the preparation of this session. This method can be
+         * used to speed up the start of the playback. Once the preparation is
+         * done, the session will change its playback state to
+         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+         * start playback. If the preparation is not needed, {@link #playFromMediaId} can
+         * be directly called without this method.
+         *
+         * @param mediaId The id of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be prepared.
+         */
+        public abstract void prepareFromMediaId(String mediaId, Bundle extras);
+
+        /**
+         * Request that the player prepare playback for a specific search query.
+         * An empty or null query should be treated as a request to prepare any
+         * music. In other words, other session can continue to play during
+         * the preparation of this session. This method can be used to speed up the start of the
+         * playback. Once the preparation is done, the session will change its playback state to
+         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+         * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly
+         * called without this method.
+         *
+         * @param query The search query.
+         * @param extras Optional extras that can include extra information
+         *               about the query.
+         */
+        public abstract void prepareFromSearch(String query, Bundle extras);
+
+        /**
+         * Request that the player prepare playback for a specific {@link Uri}.
+         * In other words, other session can continue to play during the preparation of this
+         * session. This method can be used to speed up the start of the playback.
+         * Once the preparation is done, the session will change its playback state to
+         * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+         * start playback. If the preparation is not needed, {@link #playFromUri} can be directly
+         * called without this method.
+         *
+         * @param uri The URI of the requested media.
+         * @param extras Optional extras that can include extra information about the media item
+         *               to be prepared.
+         */
+        public abstract void prepareFromUri(Uri uri, Bundle extras);
+
+        /**
          * Request that the player start its playback at its current position.
          */
         public abstract void play();
@@ -1007,6 +1067,42 @@
         }
 
         @Override
+        public void prepare() {
+            try {
+                mBinder.prepare();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepare. " + e);
+            }
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            try {
+                mBinder.prepareFromMediaId(mediaId, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromMediaId. " + e);
+            }
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            try {
+                mBinder.prepareFromSearch(query, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromSearch. " + e);
+            }
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            try {
+                mBinder.prepareFromUri(uri, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Dead object in prepareFromUri. " + e);
+            }
+        }
+
+        @Override
         public void play() {
             try {
                 mBinder.play();
@@ -1270,6 +1366,35 @@
         }
 
         @Override
+        public void prepare() {
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
+            bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+            sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
+        }
+
+        @Override
         public void play() {
             MediaControllerCompatApi21.TransportControls.play(mControlsObj);
         }
@@ -1387,4 +1512,52 @@
                     extras);
         }
     }
+
+    static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
+
+        public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
+            super(context, session);
+        }
+
+        public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
+                throws RemoteException {
+            super(context, sessionToken);
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+            return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
+        }
+    }
+
+    static class TransportControlsApi24 extends TransportControlsApi23 {
+
+        public TransportControlsApi24(Object controlsObj) {
+            super(controlsObj);
+        }
+
+        @Override
+        public void prepare() {
+            MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
+        }
+
+        @Override
+        public void prepareFromMediaId(String mediaId, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
+                    mControlsObj, mediaId, extras);
+        }
+
+        @Override
+        public void prepareFromSearch(String query, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromSearch(
+                    mControlsObj, query, extras);
+        }
+
+        @Override
+        public void prepareFromUri(Uri uri, Bundle extras) {
+            MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
+        }
+    }
+
 }
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
index 2404a89..7156645 100644
--- a/v4/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -108,26 +108,56 @@
 
     /**
      * Custom action to invoke playFromUri() for the forward compatibility.
-     *
-     * @hide
      */
-    public static final String ACTION_PLAY_FROM_URI =
+    static final String ACTION_PLAY_FROM_URI =
             "android.support.v4.media.session.action.PLAY_FROM_URI";
 
     /**
-     * Argument for use with {@link #ACTION_PLAY_FROM_URI} indicating URI to play.
-     *
-     * @hide
+     * Custom action to invoke prepare() for the forward compatibility.
      */
-    public static final String ACTION_ARGUMENT_URI =
+    static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
+
+    /**
+     * Custom action to invoke prepareFromMediaId() for the forward compatibility.
+     */
+    static final String ACTION_PREPARE_FROM_MEDIA_ID =
+            "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
+
+    /**
+     * Custom action to invoke prepareFromSearch() for the forward compatibility.
+     */
+    static final String ACTION_PREPARE_FROM_SEARCH =
+            "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
+
+    /**
+     * Custom action to invoke prepareFromUri() for the forward compatibility.
+     */
+    static final String ACTION_PREPARE_FROM_URI =
+            "android.support.v4.media.session.action.PREPARE_FROM_URI";
+
+    /**
+     * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
+     */
+    static final String ACTION_ARGUMENT_MEDIA_ID =
+            "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
+
+    /**
+     * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
+     */
+    static final String ACTION_ARGUMENT_QUERY =
+            "android.support.v4.media.session.action.ARGUMENT_QUERY";
+
+    /**
+     * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
+     * indicating URI to play.
+     */
+    static final String ACTION_ARGUMENT_URI =
             "android.support.v4.media.session.action.ARGUMENT_URI";
 
     /**
-     * Argument for use with {@link #ACTION_PLAY_FROM_URI} indicating extra bundle.
-     *
-     * @hide
+     * Argument for use with various actions indicating extra bundle.
      */
-    public static final String ACTION_ARGUMENT_EXTRAS =
+    static final String ACTION_ARGUMENT_EXTRAS =
             "android.support.v4.media.session.action.ARGUMENT_EXTRAS";
 
     /**
@@ -488,6 +518,17 @@
     }
 
     /**
+     * Returns the name of the package that sent the last media button, transport control, or
+     * command from controllers and the system. This is only valid while in a request callback, such
+     * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
+     *
+     * @hide
+     */
+    public String getCallingPackage() {
+        return mImpl.getCallingPackage();
+    }
+
+    /**
      * 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.
@@ -533,7 +574,9 @@
         final Object mCallbackObj;
 
         public Callback() {
-            if (android.os.Build.VERSION.SDK_INT >= 23) {
+            if (android.os.Build.VERSION.SDK_INT >= 24) {
+                mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
+            } else if (android.os.Build.VERSION.SDK_INT >= 23) {
                 mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
             } else if (android.os.Build.VERSION.SDK_INT >= 21) {
                 mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
@@ -565,6 +608,51 @@
         }
 
         /**
+         * Override to handle requests to prepare playback. During the preparation, a session
+         * should not hold audio focus in order to allow other session play seamlessly.
+         * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
+         * after the preparation is done.
+         */
+        public void onPrepare() {
+        }
+
+        /**
+         * Override to handle requests to prepare for playing a specific mediaId that was provided
+         * by your app. During the preparation, a session should not hold audio focus in order to
+         * allow other session play seamlessly. The state of playback should be updated to
+         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
+         * of the prepared content should start in the implementation of {@link #onPlay}. Override
+         * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
+         */
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to prepare playback from a search query. An
+         * empty query indicates that the app may prepare any music. The
+         * implementation should attempt to make a smart choice about what to
+         * play. During the preparation, a session should not hold audio focus in order to allow
+         * other session play seamlessly. The state of playback should be updated to
+         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
+         * The playback of the prepared content should start in the implementation of
+         * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
+         * starting playback without preparation.
+         */
+        public void onPrepareFromSearch(String query, Bundle extras) {
+        }
+
+        /**
+         * Override to handle requests to prepare a specific media item represented by a URI.
+         * During the preparation, a session should not hold audio focus in order to allow other
+         * session play seamlessly. The state of playback should be updated to
+         * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
+         * the prepared content should start in the implementation of {@link #onPlay}. Override
+         * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
+         */
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+        }
+
+        /**
          * Override to handle requests to begin playback.
          */
         public void onPlay() {
@@ -738,9 +826,23 @@
             @Override
             public void onCustomAction(String action, Bundle extras) {
                 if (action.equals(ACTION_PLAY_FROM_URI)) {
-                    Uri uri = (Uri) extras.getParcelable(ACTION_ARGUMENT_URI);
-                    Bundle bundle = (Bundle) extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
+                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
+                    Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
                     Callback.this.onPlayFromUri(uri, bundle);
+                } else if (action.equals(ACTION_PREPARE)) {
+                    Callback.this.onPrepare();
+                } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
+                    String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
+                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+                    Callback.this.onPrepareFromMediaId(mediaId, bundle);
+                } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
+                    String query = extras.getString(ACTION_ARGUMENT_QUERY);
+                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+                    Callback.this.onPrepareFromSearch(query, bundle);
+                } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
+                    Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
+                    Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+                    Callback.this.onPrepareFromUri(uri, bundle);
                 } else {
                     Callback.this.onCustomAction(action, extras);
                 }
@@ -754,6 +856,29 @@
                 Callback.this.onPlayFromUri(uri, extras);
             }
         }
+
+        private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
+
+            @Override
+            public void onPrepare() {
+                Callback.this.onPrepare();
+            }
+
+            @Override
+            public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+                Callback.this.onPrepareFromMediaId(mediaId, extras);
+            }
+
+            @Override
+            public void onPrepareFromSearch(String query, Bundle extras) {
+                Callback.this.onPrepareFromSearch(query, extras);
+            }
+
+            @Override
+            public void onPrepareFromUri(Uri uri, Bundle extras) {
+                Callback.this.onPrepareFromUri(uri, extras);
+            }
+        }
     }
 
     /**
@@ -1026,6 +1151,8 @@
         Object getMediaSession();
 
         Object getRemoteControlClient();
+
+        String getCallingPackage();
     }
 
     static class MediaSessionImplBase implements MediaSessionImpl {
@@ -1363,6 +1490,11 @@
         }
 
         @Override
+        public String getCallingPackage() {
+            return null;
+        }
+
+        @Override
         public void setRatingType(@RatingCompat.Style int type) {
             mRatingType = type;
         }
@@ -1681,6 +1813,26 @@
             }
 
             @Override
+            public void prepare() throws RemoteException {
+                postToHandler(MessageHandler.MSG_PREPARE);
+            }
+
+            @Override
+            public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
+                postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+            }
+
+            @Override
+            public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
+                postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
+            }
+
+            @Override
+            public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
+                postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
+            }
+
+            @Override
             public void play() throws RemoteException {
                 postToHandler(MessageHandler.MSG_PLAY);
             }
@@ -1806,24 +1958,28 @@
 
         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;
-            private static final int MSG_PLAY_URI = 18;
+            private static final int MSG_COMMAND = 1;
+            private static final int MSG_ADJUST_VOLUME = 2;
+            private static final int MSG_PREPARE = 3;
+            private static final int MSG_PREPARE_MEDIA_ID = 4;
+            private static final int MSG_PREPARE_SEARCH = 5;
+            private static final int MSG_PREPARE_URI = 6;
+            private static final int MSG_PLAY = 7;
+            private static final int MSG_PLAY_MEDIA_ID = 8;
+            private static final int MSG_PLAY_SEARCH = 9;
+            private static final int MSG_PLAY_URI = 10;
+            private static final int MSG_SKIP_TO_ITEM = 11;
+            private static final int MSG_PAUSE = 12;
+            private static final int MSG_STOP = 13;
+            private static final int MSG_NEXT = 14;
+            private static final int MSG_PREVIOUS = 15;
+            private static final int MSG_FAST_FORWARD = 16;
+            private static final int MSG_REWIND = 17;
+            private static final int MSG_SEEK_TO = 18;
+            private static final int MSG_RATE = 19;
+            private static final int MSG_CUSTOM_ACTION = 20;
+            private static final int MSG_MEDIA_BUTTON = 21;
+            private static final int MSG_SET_VOLUME = 22;
 
             // KeyEvent constants only available on API 11+
             private static final int KEYCODE_MEDIA_PAUSE = 127;
@@ -1858,6 +2014,31 @@
                     return;
                 }
                 switch (msg.what) {
+                    case MSG_COMMAND:
+                        Command cmd = (Command) msg.obj;
+                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
+                        break;
+                    case MSG_MEDIA_BUTTON:
+                        KeyEvent keyEvent = (KeyEvent) msg.obj;
+                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+                        // Let the Callback handle events first before using the default behavior
+                        if (!cb.onMediaButtonEvent(intent)) {
+                            onMediaButtonEvent(keyEvent, cb);
+                        }
+                        break;
+                    case MSG_PREPARE:
+                        cb.onPrepare();
+                        break;
+                    case MSG_PREPARE_MEDIA_ID:
+                        cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_PREPARE_SEARCH:
+                        cb.onPrepareFromSearch((String) msg.obj, msg.getData());
+                        break;
+                    case MSG_PREPARE_URI:
+                        cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
+                        break;
                     case MSG_PLAY:
                         cb.onPlay();
                         break;
@@ -1900,19 +2081,6 @@
                     case MSG_CUSTOM_ACTION:
                         cb.onCustomAction((String) msg.obj, msg.getData());
                         break;
-                    case MSG_MEDIA_BUTTON:
-                        KeyEvent keyEvent = (KeyEvent) msg.obj;
-                        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
-                        // Let the Callback handle events first before using the default behavior
-                        if (!cb.onMediaButtonEvent(intent)) {
-                            onMediaButtonEvent(keyEvent, cb);
-                        }
-                        break;
-                    case MSG_COMMAND:
-                        Command cmd = (Command) msg.obj;
-                        cb.onCommand(cmd.command, cmd.extras, cmd.stub);
-                        break;
                     case MSG_ADJUST_VOLUME:
                         adjustVolume((int) msg.obj, 0);
                         break;
@@ -2110,5 +2278,14 @@
         public Object getRemoteControlClient() {
             return null;
         }
+
+        @Override
+        public String getCallingPackage() {
+            if (android.os.Build.VERSION.SDK_INT < 24) {
+                return null;
+            } else {
+                return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
+            }
+        }
     }
 }
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
index 05ec278..e3bda8c 100644
--- a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -43,7 +43,8 @@
     @IntDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
             ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
             ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
-            ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI})
+            ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
+            ACTION_PREPARE_FROM_MEDIA_ID, ACTION_PREPARE_FROM_SEARCH, ACTION_PREPARE_FROM_URI})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Actions {}
 
@@ -137,6 +138,7 @@
      * @see Builder#setActions(long)
      */
     public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12;
+
     /**
      * Indicates this session supports the play from URI command.
      *
@@ -145,6 +147,34 @@
     public static final long ACTION_PLAY_FROM_URI = 1 << 13;
 
     /**
+     * Indicates this session supports the prepare command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PREPARE = 1 << 14;
+
+    /**
+     * Indicates this session supports the prepare from media id command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PREPARE_FROM_MEDIA_ID = 1 << 15;
+
+    /**
+     * Indicates this session supports the prepare from search command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PREPARE_FROM_SEARCH = 1 << 16;
+
+    /**
+     * Indicates this session supports the prepare from URI command.
+     *
+     * @see Builder#setActions(long)
+     */
+    public static final long ACTION_PREPARE_FROM_URI = 1 << 17;
+
+    /**
      * @hide
      */
     @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
@@ -398,6 +428,10 @@
      * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
      * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
      * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
      * </ul>
      */
     @Actions
@@ -904,6 +938,10 @@
          * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
          * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
          * <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
          * </ul>
          *
          * @return this
diff --git a/v4/java/android/support/v4/net/TrafficStatsCompat.java b/v4/java/android/support/v4/net/TrafficStatsCompat.java
index ea495b1..57b8a7e 100644
--- a/v4/java/android/support/v4/net/TrafficStatsCompat.java
+++ b/v4/java/android/support/v4/net/TrafficStatsCompat.java
@@ -18,6 +18,7 @@
 
 import android.os.Build;
 
+import java.net.DatagramSocket;
 import java.net.Socket;
 import java.net.SocketException;
 
@@ -35,6 +36,8 @@
         void setThreadStatsTag(int tag);
         void tagSocket(Socket socket) throws SocketException;
         void untagSocket(Socket socket) throws SocketException;
+        void tagDatagramSocket(DatagramSocket socket) throws SocketException;
+        void untagDatagramSocket(DatagramSocket socket) throws SocketException;
     }
 
     static class BaseTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -79,6 +82,14 @@
         @Override
         public void untagSocket(Socket socket) {
         }
+
+        @Override
+        public void tagDatagramSocket(DatagramSocket socket) {
+        }
+
+        @Override
+        public void untagDatagramSocket(DatagramSocket socket) {
+        }
     }
 
     static class IcsTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -116,12 +127,36 @@
         public void untagSocket(Socket socket) throws SocketException {
             TrafficStatsCompatIcs.untagSocket(socket);
         }
+
+        @Override
+        public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+            TrafficStatsCompatIcs.tagDatagramSocket(socket);
+        }
+
+        @Override
+        public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+            TrafficStatsCompatIcs.untagDatagramSocket(socket);
+        }
+    }
+
+    static class Api24TrafficStatsCompatImpl extends IcsTrafficStatsCompatImpl {
+        @Override
+        public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+            TrafficStatsCompatApi24.tagDatagramSocket(socket);
+        }
+
+        @Override
+        public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+            TrafficStatsCompatApi24.untagDatagramSocket(socket);
+        }
     }
 
     private static final TrafficStatsCompatImpl IMPL;
 
     static {
-        if (Build.VERSION.SDK_INT >= 14) {
+        if ("N".equals(Build.VERSION.CODENAME)) {
+            IMPL = new Api24TrafficStatsCompatImpl();
+        } else if (Build.VERSION.SDK_INT >= 14) {
             IMPL = new IcsTrafficStatsCompatImpl();
         } else {
             IMPL = new BaseTrafficStatsCompatImpl();
@@ -201,5 +236,25 @@
         IMPL.untagSocket(socket);
     }
 
+    /**
+     * Tag the given {@link DatagramSocket} with any statistics parameters
+     * active for the current thread. Subsequent calls always replace any
+     * existing parameters. When finished, call
+     * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics
+     * parameters.
+     *
+     * @see #setThreadStatsTag(int)
+     */
+    public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+        IMPL.tagDatagramSocket(socket);
+    }
+
+    /**
+     * Remove any statistics parameters from the given {@link DatagramSocket}.
+     */
+    public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+        IMPL.untagDatagramSocket(socket);
+    }
+
     private TrafficStatsCompat() {}
 }
diff --git a/v4/java/android/support/v4/os/BuildCompat.java b/v4/java/android/support/v4/os/BuildCompat.java
new file mode 100644
index 0000000..513e153
--- /dev/null
+++ b/v4/java/android/support/v4/os/BuildCompat.java
@@ -0,0 +1,40 @@
+/*
+ * 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.Build.VERSION;
+
+/**
+ * BuildCompat contains additional platform version checking methods for
+ * testing compatibility with new features.
+ */
+public class BuildCompat {
+    private BuildCompat() {
+    }
+
+    /**
+     * Check if the device is running on the Android N release or newer.
+     * This method is suitable for use with preview SDKs and associated
+     * prerelease device builds.
+     *
+     * @return true if N APIs are available for use
+     */
+    public static boolean isAtLeastN() {
+        return "N".equals(VERSION.CODENAME);
+    }
+}
diff --git a/v4/java/android/support/v4/os/UserManagerCompat.java b/v4/java/android/support/v4/os/UserManagerCompat.java
new file mode 100644
index 0000000..436bfc8
--- /dev/null
+++ b/v4/java/android/support/v4/os/UserManagerCompat.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.os;
+
+import android.content.Context;
+
+/**
+ * Helper for accessing features in {@link android.os.UserManager}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class UserManagerCompat {
+    /**
+     * Return whether the calling user is running in a "locked" state. A user is
+     * unlocked only after they've entered their credentials (such as a lock
+     * pattern or PIN), and credential-encrypted private app data storage is
+     * available.
+     *
+     * @removed
+     */
+    @Deprecated
+    public static boolean isUserRunningAndLocked(Context context) {
+        return !isUserUnlocked(context);
+    }
+
+    /**
+     * Return whether the calling user is running in an "unlocked" state. A user
+     * is unlocked only after they've entered their credentials (such as a lock
+     * pattern or PIN), and credential-encrypted private app data storage is
+     * available.
+     *
+     * @removed
+     */
+    @Deprecated
+    public static boolean isUserRunningAndUnlocked(Context context) {
+        return isUserUnlocked(context);
+    }
+
+    /**
+     * Return whether the calling user is running in an "unlocked" state. A user
+     * is unlocked only after they've entered their credentials (such as a lock
+     * pattern or PIN), and credential-encrypted private app data storage is
+     * available.
+     */
+    public static boolean isUserUnlocked(Context context) {
+        if (BuildCompat.isAtLeastN()) {
+            return UserManagerCompatApi24.isUserUnlocked(context);
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/print/PrintHelper.java b/v4/java/android/support/v4/print/PrintHelper.java
index 23429ec..b9e5b14 100644
--- a/v4/java/android/support/v4/print/PrintHelper.java
+++ b/v4/java/android/support/v4/print/PrintHelper.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Build;
+
 import android.net.Uri;
 
 import java.io.FileNotFoundException;
@@ -151,13 +152,14 @@
     }
 
     /**
-     * Implementation used on KitKat (and above)
+     * Generic implementation for KitKat to Api24
      */
-    private static final class PrintHelperKitkatImpl implements PrintHelperVersionImpl {
-        private final PrintHelperKitkat mPrintHelper;
+    private static class PrintHelperImpl<RealHelper extends PrintHelperKitkat>
+            implements PrintHelperVersionImpl {
+        private final RealHelper mPrintHelper;
 
-        PrintHelperKitkatImpl(Context context) {
-            mPrintHelper = new PrintHelperKitkat(context);
+        protected PrintHelperImpl(RealHelper helper) {
+            mPrintHelper = helper;
         }
 
         @Override
@@ -193,9 +195,9 @@
         @Override
         public void printBitmap(String jobName, Bitmap bitmap,
                 final OnPrintFinishCallback callback) {
-            PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+            RealHelper.OnPrintFinishCallback delegateCallback = null;
             if (callback != null) {
-                delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+                delegateCallback = new RealHelper.OnPrintFinishCallback() {
                     @Override
                     public void onFinish() {
                         callback.onFinish();
@@ -208,9 +210,9 @@
         @Override
         public void printBitmap(String jobName, Uri imageFile,
                 final OnPrintFinishCallback callback) throws FileNotFoundException {
-            PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+            RealHelper.OnPrintFinishCallback delegateCallback = null;
             if (callback != null) {
-                delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+                delegateCallback = new RealHelper.OnPrintFinishCallback() {
                     @Override
                     public void onFinish() {
                         callback.onFinish();
@@ -222,6 +224,43 @@
     }
 
     /**
+     * Implementation used on KitKat
+     */
+    private static final class PrintHelperKitkatImpl extends PrintHelperImpl<PrintHelperKitkat> {
+        PrintHelperKitkatImpl(Context context) {
+            super(new PrintHelperKitkat(context));
+        }
+    }
+
+    /**
+     * Implementation used on Api20 to Api22
+     */
+    private static final class PrintHelperApi20Impl extends PrintHelperImpl<PrintHelperApi20> {
+        PrintHelperApi20Impl(Context context) {
+            super(new PrintHelperApi20(context));
+        }
+    }
+
+    /**
+     * Implementation used on Api23
+     */
+    private static final class PrintHelperApi23Impl extends PrintHelperImpl<PrintHelperApi23> {
+        PrintHelperApi23Impl(Context context) {
+            super(new PrintHelperApi23(context));
+        }
+    }
+
+
+    /**
+     * Implementation used on Api24 and above
+     */
+    private static final class PrintHelperApi24Impl extends PrintHelperImpl<PrintHelperApi24> {
+        PrintHelperApi24Impl(Context context) {
+            super(new PrintHelperApi24(context));
+        }
+    }
+
+    /**
      * Returns the PrintHelper that can be used to print images.
      *
      * @param context A context for accessing system resources.
@@ -229,7 +268,15 @@
      */
     public PrintHelper(Context context) {
         if (systemSupportsPrint()) {
-            mImpl = new PrintHelperKitkatImpl(context);
+            if (Build.VERSION.CODENAME.equals("N") ) {
+                mImpl = new PrintHelperApi24Impl(context);
+            } else if (Build.VERSION.SDK_INT >= 23) {
+                mImpl = new PrintHelperApi23Impl(context);
+            } else if (Build.VERSION.SDK_INT >= 20) {
+                mImpl = new PrintHelperApi20Impl(context);
+            } else {
+                mImpl = new PrintHelperKitkatImpl(context);
+            }
         } else {
             mImpl = new PrintHelperStubImpl();
         }
@@ -350,4 +397,4 @@
             throws FileNotFoundException {
         mImpl.printBitmap(jobName, imageFile, callback);
     }
-}
\ No newline at end of file
+}
diff --git a/v4/java/android/support/v4/util/SimpleArrayMap.java b/v4/java/android/support/v4/util/SimpleArrayMap.java
index 1b1577a..c7d4b5d 100644
--- a/v4/java/android/support/v4/util/SimpleArrayMap.java
+++ b/v4/java/android/support/v4/util/SimpleArrayMap.java
@@ -522,17 +522,42 @@
     /**
      * {@inheritDoc}
      *
-     * <p>This implementation returns false if the object is not a map, or
-     * if the maps have different sizes. Otherwise, for each key in this map,
-     * values of both maps are compared. If the values for any key are not
-     * equal, the method returns false, otherwise it returns true.
+     * <p>This implementation returns false if the object is not a Map or
+     * SimpleArrayMap, or if the maps have different sizes. Otherwise, for each
+     * key in this map, values of both maps are compared. If the values for any
+     * key are not equal, the method returns false, otherwise it returns true.
      */
     @Override
     public boolean equals(Object object) {
         if (this == object) {
             return true;
         }
-        if (object instanceof Map) {
+        if (object instanceof SimpleArrayMap) {
+            SimpleArrayMap<?, ?> map = (SimpleArrayMap<?, ?>) object;
+            if (size() != map.size()) {
+                return false;
+            }
+
+            try {
+                for (int i=0; i<mSize; i++) {
+                    K key = keyAt(i);
+                    V mine = valueAt(i);
+                    Object theirs = map.get(key);
+                    if (mine == null) {
+                        if (theirs != null || !map.containsKey(key)) {
+                            return false;
+                        }
+                    } else if (!mine.equals(theirs)) {
+                        return false;
+                    }
+                }
+            } catch (NullPointerException ignored) {
+                return false;
+            } catch (ClassCastException ignored) {
+                return false;
+            }
+            return true;
+        } else if (object instanceof Map) {
             Map<?, ?> map = (Map<?, ?>) object;
             if (size() != map.size()) {
                 return false;
diff --git a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
index 0ea755e..4d0281e 100644
--- a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
+++ b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
@@ -27,6 +27,18 @@
 /**
  * Helper for accessing {@link View.AccessibilityDelegate} introduced after
  * API level 4 in a backwards compatible fashion.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
  */
 public class AccessibilityDelegateCompat {
 
diff --git a/v4/java/android/support/v4/view/AsyncLayoutInflater.java b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
new file mode 100644
index 0000000..6e88f59
--- /dev/null
+++ b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.v4.util.Pools.SynchronizedPool;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * <p>Helper class for inflating layouts asynchronously. To use, construct
+ * an instance of {@link AsyncLayoutInflater} on the UI thread and call
+ * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The
+ * {@link OnInflateFinishedListener} will be invoked on the UI thread
+ * when the inflate request has completed.
+ *
+ * <p>This is intended for parts of the UI that are created lazily or in
+ * response to user interactions. This allows the UI thread to continue
+ * to be responsive & animate while the relatively heavy inflate
+ * is being performed.
+ *
+ * <p>For a layout to be inflated asynchronously it needs to have a parent
+ * whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
+ * and all the Views being constructed as part of inflation must not create
+ * any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
+ * layout that is trying to be inflated cannot be constructed
+ * asynchronously for whatever reason, {@link AsyncLayoutInflater} will
+ * automatically fall back to inflating on the UI thread.
+ *
+ * <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is
+ * equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
+ * with attachToRoot set to false. Callers will likely want to call
+ * {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
+ * callback at a minimum.
+ *
+ * <p>This inflater does not support setting a {@link LayoutInflater.Factory}
+ * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
+ * layouts that contain fragments.
+ */
+public final class AsyncLayoutInflater {
+    private static final String TAG = "AsyncLayoutInflater";
+
+    private LayoutInflater mInflater;
+    private Handler mHandler;
+    private InflateThread mInflateThread;
+
+    public AsyncLayoutInflater(@NonNull Context context) {
+        mInflater = new BasicInflater(context);
+        mHandler = new Handler(mHandlerCallback);
+        mInflateThread = InflateThread.getInstance();
+    }
+
+    @UiThread
+    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
+            @NonNull OnInflateFinishedListener callback) {
+        if (callback == null) {
+            throw new NullPointerException("callback argument may not be null!");
+        }
+        InflateRequest request = mInflateThread.obtainRequest();
+        request.inflater = this;
+        request.resid = resid;
+        request.parent = parent;
+        request.callback = callback;
+        mInflateThread.enqueue(request);
+    }
+
+    private Callback mHandlerCallback = new Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            InflateRequest request = (InflateRequest) msg.obj;
+            if (request.view == null) {
+                request.view = mInflater.inflate(
+                        request.resid, request.parent, false);
+            }
+            request.callback.onInflateFinished(
+                    request.view, request.resid, request.parent);
+            mInflateThread.releaseRequest(request);
+            return true;
+        }
+    };
+
+    public interface OnInflateFinishedListener {
+        public void onInflateFinished(View view, int resid, ViewGroup parent);
+    }
+
+    private static class InflateRequest {
+        AsyncLayoutInflater inflater;
+        ViewGroup parent;
+        int resid;
+        View view;
+        OnInflateFinishedListener callback;
+    }
+
+    private static class BasicInflater extends LayoutInflater {
+        private static final String[] sClassPrefixList = {
+            "android.widget.",
+            "android.webkit.",
+            "android.app."
+        };
+
+        public BasicInflater(Context context) {
+            super(context);
+        }
+
+        @Override
+        public LayoutInflater cloneInContext(Context newContext) {
+            return new BasicInflater(newContext);
+        }
+
+        @Override
+        protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+            for (String prefix : sClassPrefixList) {
+                try {
+                    View view = createView(name, prefix, attrs);
+                    if (view != null) {
+                        return view;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // In this case we want to let the base class take a crack
+                    // at it.
+                }
+            }
+
+            return super.onCreateView(name, attrs);
+        }
+    }
+
+    private static class InflateThread extends Thread {
+        private static final InflateThread sInstance;
+        static {
+            sInstance = new InflateThread();
+            sInstance.start();
+        }
+
+        public static InflateThread getInstance() {
+            return sInstance;
+        }
+
+        private ArrayBlockingQueue<InflateRequest> mQueue
+                = new ArrayBlockingQueue<>(10);
+        private SynchronizedPool<InflateRequest> mRequestPool
+                = new SynchronizedPool<>(10);
+
+        @Override
+        public void run() {
+            while (true) {
+                InflateRequest request;
+                try {
+                    request = mQueue.take();
+                } catch (InterruptedException ex) {
+                    // Odd, just continue
+                    Log.w(TAG, ex);
+                    continue;
+                }
+
+                try {
+                    request.view = request.inflater.mInflater.inflate(
+                            request.resid, request.parent, false);
+                } catch (RuntimeException ex) {
+                    // Probably a Looper failure, retry on the UI thread
+                    Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI thread",
+                            ex);
+                }
+                Message.obtain(request.inflater.mHandler, 0, request)
+                        .sendToTarget();
+            }
+        }
+
+        public InflateRequest obtainRequest() {
+            InflateRequest obj = mRequestPool.acquire();
+            if (obj == null) {
+                obj = new InflateRequest();
+            }
+            return obj;
+        }
+
+        public void releaseRequest(InflateRequest obj) {
+            obj.callback = null;
+            obj.inflater = null;
+            obj.parent = null;
+            obj.resid = 0;
+            obj.view = null;
+            mRequestPool.release(obj);
+        }
+
+        public void enqueue(InflateRequest request) {
+            try {
+                mQueue.put(request);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(
+                        "Failed to enqueue async inflate request", e);
+            }
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/view/KeyEventCompat.java b/v4/java/android/support/v4/view/KeyEventCompat.java
index 02b6622..ee5a914 100644
--- a/v4/java/android/support/v4/view/KeyEventCompat.java
+++ b/v4/java/android/support/v4/view/KeyEventCompat.java
@@ -28,14 +28,14 @@
      * Interface for the full API.
      */
     interface KeyEventVersionImpl {
-        public int normalizeMetaState(int metaState);
-        public boolean metaStateHasModifiers(int metaState, int modifiers);
-        public boolean metaStateHasNoModifiers(int metaState);
-        public void startTracking(KeyEvent event);
-        public boolean isTracking(KeyEvent event);
-        public Object getKeyDispatcherState(View view);
-        public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state,
-                    Object target);
+        int normalizeMetaState(int metaState);
+        boolean metaStateHasModifiers(int metaState, int modifiers);
+        boolean metaStateHasNoModifiers(int metaState);
+        void startTracking(KeyEvent event);
+        boolean isTracking(KeyEvent event);
+        Object getKeyDispatcherState(View view);
+        boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, Object target);
+        boolean isCtrlPressed(KeyEvent event);
     }
 
     /**
@@ -113,6 +113,11 @@
                     Object target) {
             return event.dispatch(receiver);
         }
+
+        @Override
+        public boolean isCtrlPressed(KeyEvent event) {
+            return false;
+        }
     }
 
     static class EclairKeyEventVersionImpl extends BaseKeyEventVersionImpl {
@@ -156,6 +161,11 @@
         public boolean metaStateHasNoModifiers(int metaState) {
             return KeyEventCompatHoneycomb.metaStateHasNoModifiers(metaState);
         }
+
+        @Override
+        public boolean isCtrlPressed(KeyEvent event) {
+            return KeyEventCompatHoneycomb.isCtrlPressed(event);
+        }
     }
 
     /**
@@ -209,5 +219,9 @@
         return IMPL.dispatch(event, receiver, state, target);
     }
 
+    public static boolean isCtrlPressed(KeyEvent event) {
+        return IMPL.isCtrlPressed(event);
+    }
+
     private KeyEventCompat() {}
 }
diff --git a/v4/java/android/support/v4/view/MotionEventCompat.java b/v4/java/android/support/v4/view/MotionEventCompat.java
index 964a669..81d1d05 100644
--- a/v4/java/android/support/v4/view/MotionEventCompat.java
+++ b/v4/java/android/support/v4/view/MotionEventCompat.java
@@ -36,6 +36,7 @@
         public int getSource(MotionEvent event);
         float getAxisValue(MotionEvent event, int axis);
         float getAxisValue(MotionEvent event, int axis, int pointerIndex);
+        int getButtonState(MotionEvent event);
     }
 
     /**
@@ -91,6 +92,11 @@
         public float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
             return 0;
         }
+
+        @Override
+        public int getButtonState(MotionEvent event) {
+            return 0;
+        }
     }
 
     /**
@@ -145,12 +151,25 @@
         }
     }
 
+
+    /**
+     * Interface implementation for devices with at least v14 APIs.
+     */
+    private static class ICSMotionEventVersionImpl extends HoneycombMr1MotionEventVersionImpl {
+        @Override
+        public int getButtonState(MotionEvent event) {
+            return MotionEventCompatICS.getButtonState(event);
+        }
+    }
+
     /**
      * Select the correct implementation to use for the current platform.
      */
     static final MotionEventVersionImpl IMPL;
     static {
-        if (Build.VERSION.SDK_INT >= 12) {
+        if (Build.VERSION.SDK_INT >= 14) {
+            IMPL = new ICSMotionEventVersionImpl();
+        } else if (Build.VERSION.SDK_INT >= 12) {
             IMPL = new HoneycombMr1MotionEventVersionImpl();
         } else if (Build.VERSION.SDK_INT >= 9) {
             IMPL = new GingerbreadMotionEventVersionImpl();
@@ -339,6 +358,16 @@
     public static final int AXIS_TILT = 25;
 
     /**
+     * Synonym for {@link MotionEvent#AXIS_RELATIVE_X}.
+     */
+    public static final int AXIS_RELATIVE_X = 27;
+
+    /**
+     * Synonym for {@link MotionEvent#AXIS_RELATIVE_Y}.
+     */
+    public static final int AXIS_RELATIVE_Y = 28;
+
+    /**
      * Synonym for {@link MotionEvent#AXIS_GENERIC_1}.
      */
     public static final int AXIS_GENERIC_1 = 32;
@@ -419,6 +448,11 @@
     public static final int AXIS_GENERIC_16 = 47;
 
     /**
+     * Synonym for {@link MotionEvent#BUTTON_PRIMARY}.
+     */
+    public static final int BUTTON_PRIMARY = 1;
+
+    /**
      * Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
      * portion.
      */
@@ -489,6 +523,15 @@
     }
 
     /**
+     * Determines whether the event is from the given source.
+     * @param source The input source to check against.
+     * @return Whether the event is from the given source.
+     */
+    public static boolean isFromSource(MotionEvent event, int source) {
+        return (getSource(event) & source) == source;
+    }
+
+    /**
      * Get axis value for the first pointer index (may be an
      * arbitrary pointer identifier).
      *
@@ -517,5 +560,14 @@
         return IMPL.getAxisValue(event, axis, pointerIndex);
     }
 
+    /**
+     *
+     * @param event
+     * @return
+     */
+    public static int getButtonState(MotionEvent event) {
+        return IMPL.getButtonState(event);
+    }
+
     private MotionEventCompat() {}
 }
diff --git a/v4/java/android/support/v4/view/PointerIconCompat.java b/v4/java/android/support/v4/view/PointerIconCompat.java
new file mode 100644
index 0000000..2e78ffa
--- /dev/null
+++ b/v4/java/android/support/v4/view/PointerIconCompat.java
@@ -0,0 +1,220 @@
+/**
+ * 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.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Helper for accessing features in {@link android.view.PointerIcon} introduced after API
+ * level 4 in a backwards compatible fashion.
+ */
+public final class PointerIconCompat {
+    /** Synonym for {@link android.view.PointerIcon#STYLE_NULL} */
+    public static final int STYLE_NULL = 0;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_ARROW} */
+    public static final int STYLE_ARROW = 1000;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_CONTEXT_MENU} */
+    public static final int STYLE_CONTEXT_MENU = 1001;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_HAND} */
+    public static final int STYLE_HAND = 1002;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_HELP} */
+    public static final int STYLE_HELP = 1003;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_WAIT} */
+    public static final int STYLE_WAIT = 1004;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_CELL} */
+    public static final int STYLE_CELL = 1006;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_CROSSHAIR} */
+    public static final int STYLE_CROSSHAIR = 1007;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_TEXT} */
+    public static final int STYLE_TEXT = 1008;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_VERTICAL_TEXT} */
+    public static final int STYLE_VERTICAL_TEXT = 1009;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_ALIAS} */
+    public static final int STYLE_ALIAS = 1010;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_COPY} */
+    public static final int STYLE_COPY = 1011;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_NO_DROP} */
+    public static final int STYLE_NO_DROP = 1012;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_ALL_SCROLL} */
+    public static final int STYLE_ALL_SCROLL = 1013;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_HORIZONTAL_DOUBLE_ARROW} */
+    public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_VERTICAL_DOUBLE_ARROW} */
+    public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW} */
+    public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW} */
+    public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_ZOOM_IN} */
+    public static final int STYLE_ZOOM_IN = 1018;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_ZOOM_OUT} */
+    public static final int STYLE_ZOOM_OUT = 1019;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_GRAB} */
+    public static final int STYLE_GRAB = 1020;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_GRABBING} */
+    public static final int STYLE_GRABBING = 1021;
+
+    /** Synonym for {@link android.view.PointerIcon#STYLE_DEFAULT} */
+    public static final int STYLE_DEFAULT = STYLE_ARROW;
+
+
+    private Object mPointerIcon;
+
+    private PointerIconCompat(Object pointerIcon) {
+        mPointerIcon = pointerIcon;
+    }
+
+    private static PointerIconCompat create(Object pointerIcon) {
+        return new PointerIconCompat(pointerIcon);
+    }
+
+    /**
+     * @hide
+     */
+    public Object getPointerIcon() {
+        return mPointerIcon;
+    }
+
+    interface PointerIconCompatImpl {
+        Object getSystemIcon(Context context, int style);
+        Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY);
+        Object loadCustomIcon(Resources resources, int resourceId);
+    }
+
+    static class BasePointerIconCompatImpl implements PointerIconCompatImpl {
+        @Override
+        public Object getSystemIcon(Context context, int style) {
+            return null;
+        }
+
+        @Override
+        public Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+            return null;
+        }
+
+        @Override
+        public Object loadCustomIcon(Resources resources, int resourceId) {
+            return null;
+        }
+    }
+
+    static class Api24PointerIconCompatImpl extends BasePointerIconCompatImpl {
+        @Override
+        public Object getSystemIcon(Context context, int style) {
+            return PointerIconCompatApi24.getSystemIcon(context, style);
+        }
+
+        @Override
+        public Object createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+            return PointerIconCompatApi24.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+        }
+
+        @Override
+        public Object loadCustomIcon(Resources resources, int resourceId) {
+            return PointerIconCompatApi24.loadCustomIcon(resources, resourceId);
+        }
+    }
+
+    static final PointerIconCompatImpl IMPL;
+    static {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new Api24PointerIconCompatImpl();
+        } else {
+            IMPL = new BasePointerIconCompatImpl();
+        }
+    }
+
+    /**
+     * Gets a system pointer icon for the given style.
+     * If style is not recognized, returns the default pointer icon.
+     *
+     * @param context The context.
+     * @param style The pointer icon style.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIconCompat getSystemIcon(Context context, int style) {
+        return create(IMPL.getSystemIcon(context, style));
+    }
+
+    /**
+     * Creates a custom pointer from the given bitmap and hotspot information.
+     *
+     * @param bitmap The bitmap for the icon.
+     * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getWidth()) range.
+     * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getHeight()) range.
+     * @return A pointer icon for this bitmap.
+     *
+     * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+     *         parameters are invalid.
+     */
+    public static PointerIconCompat createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        return create(IMPL.createCustomIcon(bitmap, hotSpotX, hotSpotY));
+    }
+
+    /**
+     * Loads a custom pointer icon from an XML resource.
+     * <p>
+     * The XML resource should have the following form:
+     * <code>
+     * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+     * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+     *   android:bitmap="@drawable/my_pointer_bitmap"
+     *   android:hotSpotX="24"
+     *   android:hotSpotY="24" /&gt;
+     * </code>
+     * </p>
+     *
+     * @param resources The resources object.
+     * @param resourceId The resource id.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if resources is null.
+     * @throws Resources.NotFoundException if the resource was not found or the drawable
+     * linked in the resource was not found.
+     */
+    public static PointerIconCompat loadCustomIcon(Resources resources, int resourceId) {
+        return create(IMPL.loadCustomIcon(resources, resourceId));
+    }
+}
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index 49500f8..8ef49a9 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -28,6 +28,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
 import android.util.Log;
@@ -49,9 +50,24 @@
  * Helper for accessing features in {@link View} introduced after API
  * level 4 in a backwards compatible fashion.
  */
-public final class ViewCompat {
+public class ViewCompat {
     private static final String TAG = "ViewCompat";
 
+    /** @hide */
+    @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN,
+            View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusDirection {}
+
+    /** @hide */
+    @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusRealDirection {}
+
+    /** @hide */
+    @IntDef({View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FocusRelativeDirection {}
 
     /** @hide */
     @IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_NEVER})
@@ -292,36 +308,36 @@
     /**
      * Scroll indicator direction for the top edge of the view.
      *
-     * @see #setScrollIndicators(int)
-     * @see #setScrollIndicators(int, int)
-     * @see #getScrollIndicators()
+     * @see #setScrollIndicators(View, int)
+     * @see #setScrollIndicators(View, int, int)
+     * @see #getScrollIndicators(View)
      */
     public static final int SCROLL_INDICATOR_TOP = 0x1;
 
     /**
      * Scroll indicator direction for the bottom edge of the view.
      *
-     * @see #setScrollIndicators(int)
-     * @see #setScrollIndicators(int, int)
-     * @see #getScrollIndicators()
+     * @see #setScrollIndicators(View, int)
+     * @see #setScrollIndicators(View, int, int)
+     * @see #getScrollIndicators(View)
      */
     public static final int SCROLL_INDICATOR_BOTTOM = 0x2;
 
     /**
      * Scroll indicator direction for the left edge of the view.
      *
-     * @see #setScrollIndicators(int)
-     * @see #setScrollIndicators(int, int)
-     * @see #getScrollIndicators()
+     * @see #setScrollIndicators(View, int)
+     * @see #setScrollIndicators(View, int, int)
+     * @see #getScrollIndicators(View)
      */
     public static final int SCROLL_INDICATOR_LEFT = 0x4;
 
     /**
      * Scroll indicator direction for the right edge of the view.
      *
-     * @see #setScrollIndicators(int)
-     * @see #setScrollIndicators(int, int)
-     * @see #getScrollIndicators()
+     * @see #setScrollIndicators(View, int)
+     * @see #setScrollIndicators(View, int, int)
+     * @see #getScrollIndicators(View)
      */
     public static final int SCROLL_INDICATOR_RIGHT = 0x8;
 
@@ -337,93 +353,93 @@
     /**
      * Scroll indicator direction for the ending edge of the view.
      *
-     * @see #setScrollIndicators(int)
-     * @see #setScrollIndicators(int, int)
-     * @see #getScrollIndicators()
+     * @see #setScrollIndicators(View, int)
+     * @see #setScrollIndicators(View, int, int)
+     * @see #getScrollIndicators(View)
      */
     public static final int SCROLL_INDICATOR_END = 0x20;
 
     interface ViewCompatImpl {
-        public boolean canScrollHorizontally(View v, int direction);
-        public boolean canScrollVertically(View v, int direction);
-        public int getOverScrollMode(View v);
-        public void setOverScrollMode(View v, int mode);
-        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, @Nullable AccessibilityDelegateCompat delegate);
-        public boolean hasAccessibilityDelegate(View v);
-        public boolean hasTransientState(View view);
-        public void setHasTransientState(View view, boolean hasTransientState);
-        public void postInvalidateOnAnimation(View view);
-        public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
-        public void postOnAnimation(View view, Runnable action);
-        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);
-        public void setLayerType(View view, int layerType, Paint paint);
-        public int getLayerType(View view);
-        public int getLabelFor(View view);
-        public void setLabelFor(View view, int id);
-        public void setLayerPaint(View view, Paint paint);
-        public int getLayoutDirection(View view);
-        public void setLayoutDirection(View view, int layoutDirection);
-        public ViewParent getParentForAccessibility(View view);
-        public boolean isOpaque(View view);
-        public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
-        public int getMeasuredWidthAndState(View view);
-        public int getMeasuredHeightAndState(View view);
-        public int getMeasuredState(View view);
-        public int getAccessibilityLiveRegion(View view);
-        public void setAccessibilityLiveRegion(View view, int mode);
-        public int getPaddingStart(View view);
-        public int getPaddingEnd(View view);
-        public void setPaddingRelative(View view, int start, int top, int end, int bottom);
-        public void dispatchStartTemporaryDetach(View view);
-        public void dispatchFinishTemporaryDetach(View view);
-        public float getX(View view);
-        public float getY(View view);
-        public float getRotation(View view);
-        public float getRotationX(View view);
-        public float getRotationY(View view);
-        public float getScaleX(View view);
-        public float getScaleY(View view);
-        public float getTranslationX(View view);
-        public float getTranslationY(View view);
-        public int getMinimumWidth(View view);
-        public int getMinimumHeight(View view);
-        public ViewPropertyAnimatorCompat animate(View view);
-        public void setRotation(View view, float value);
-        public void setRotationX(View view, float value);
-        public void setRotationY(View view, float value);
-        public void setScaleX(View view, float value);
-        public void setScaleY(View view, float value);
-        public void setTranslationX(View view, float value);
-        public void setTranslationY(View view, float value);
-        public void setX(View view, float value);
-        public void setY(View view, float value);
-        public void setAlpha(View view, float value);
-        public void setPivotX(View view, float value);
-        public void setPivotY(View view, float value);
-        public float getPivotX(View view);
-        public float getPivotY(View view);
-        public void setElevation(View view, float elevation);
-        public float getElevation(View view);
-        public void setTranslationZ(View view, float translationZ);
-        public float getTranslationZ(View view);
-        public void setClipBounds(View view, Rect clipBounds);
-        public Rect getClipBounds(View view);
-        public void setTransitionName(View view, String transitionName);
-        public String getTransitionName(View view);
-        public int getWindowSystemUiVisibility(View view);
-        public void requestApplyInsets(View view);
-        public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
-        public boolean getFitsSystemWindows(View view);
-        public boolean hasOverlappingRendering(View view);
+        boolean canScrollHorizontally(View v, int direction);
+        boolean canScrollVertically(View v, int direction);
+        int getOverScrollMode(View v);
+        void setOverScrollMode(View v, int mode);
+        void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
+        void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
+        void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
+        void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
+        boolean hasAccessibilityDelegate(View v);
+        boolean hasTransientState(View view);
+        void setHasTransientState(View view, boolean hasTransientState);
+        void postInvalidateOnAnimation(View view);
+        void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
+        void postOnAnimation(View view, Runnable action);
+        void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
+        int getImportantForAccessibility(View view);
+        void setImportantForAccessibility(View view, int mode);
+        boolean isImportantForAccessibility(View view);
+        boolean performAccessibilityAction(View view, int action, Bundle arguments);
+        AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
+        float getAlpha(View view);
+        void setLayerType(View view, int layerType, Paint paint);
+        int getLayerType(View view);
+        int getLabelFor(View view);
+        void setLabelFor(View view, int id);
+        void setLayerPaint(View view, Paint paint);
+        int getLayoutDirection(View view);
+        void setLayoutDirection(View view, int layoutDirection);
+        ViewParent getParentForAccessibility(View view);
+        boolean isOpaque(View view);
+        int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
+        int getMeasuredWidthAndState(View view);
+        int getMeasuredHeightAndState(View view);
+        int getMeasuredState(View view);
+        int getAccessibilityLiveRegion(View view);
+        void setAccessibilityLiveRegion(View view, int mode);
+        int getPaddingStart(View view);
+        int getPaddingEnd(View view);
+        void setPaddingRelative(View view, int start, int top, int end, int bottom);
+        void dispatchStartTemporaryDetach(View view);
+        void dispatchFinishTemporaryDetach(View view);
+        float getX(View view);
+        float getY(View view);
+        float getRotation(View view);
+        float getRotationX(View view);
+        float getRotationY(View view);
+        float getScaleX(View view);
+        float getScaleY(View view);
+        float getTranslationX(View view);
+        float getTranslationY(View view);
+        int getMinimumWidth(View view);
+        int getMinimumHeight(View view);
+        ViewPropertyAnimatorCompat animate(View view);
+        void setRotation(View view, float value);
+        void setRotationX(View view, float value);
+        void setRotationY(View view, float value);
+        void setScaleX(View view, float value);
+        void setScaleY(View view, float value);
+        void setTranslationX(View view, float value);
+        void setTranslationY(View view, float value);
+        void setX(View view, float value);
+        void setY(View view, float value);
+        void setAlpha(View view, float value);
+        void setPivotX(View view, float value);
+        void setPivotY(View view, float value);
+        float getPivotX(View view);
+        float getPivotY(View view);
+        void setElevation(View view, float elevation);
+        float getElevation(View view);
+        void setTranslationZ(View view, float translationZ);
+        float getTranslationZ(View view);
+        void setClipBounds(View view, Rect clipBounds);
+        Rect getClipBounds(View view);
+        void setTransitionName(View view, String transitionName);
+        String getTransitionName(View view);
+        int getWindowSystemUiVisibility(View view);
+        void requestApplyInsets(View view);
+        void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
+        boolean getFitsSystemWindows(View view);
+        boolean hasOverlappingRendering(View view);
         void setFitsSystemWindows(View view, boolean fitSystemWindows);
         void jumpDrawablesToCurrentState(View v);
         void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener);
@@ -449,14 +465,18 @@
         boolean dispatchNestedPreFling(View view, float velocityX, float velocityY);
         boolean isLaidOut(View view);
         int combineMeasuredStates(int curState, int newState);
-        public float getZ(View view);
-        public boolean isAttachedToWindow(View view);
-        public boolean hasOnClickListeners(View view);
-        public void setScrollIndicators(View view, int indicators);
-        public void setScrollIndicators(View view, int indicators, int mask);
-        public int getScrollIndicators(View view);
-        public void offsetTopAndBottom(View view, int offset);
-        public void offsetLeftAndRight(View view, int offset);
+        float getZ(View view);
+        boolean isAttachedToWindow(View view);
+        boolean hasOnClickListeners(View view);
+        void setScrollIndicators(View view, int indicators);
+        void setScrollIndicators(View view, int indicators, int mask);
+        int getScrollIndicators(View view);
+        void offsetTopAndBottom(View view, int offset);
+        void offsetLeftAndRight(View view, int offset);
+        void setPointerCapture(View view);
+        boolean hasPointerCapture(View view);
+        void releasePointerCapture(View view);
+        void setPointerIcon(View view, PointerIconCompat pointerIcon);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -1066,6 +1086,26 @@
         public void offsetTopAndBottom(View view, int offset) {
             ViewCompatBase.offsetTopAndBottom(view, offset);
         }
+
+        @Override
+        public void setPointerCapture(View view) {
+            // no-op
+        }
+
+        @Override
+        public boolean hasPointerCapture(View view) {
+            return false;
+        }
+
+        @Override
+        public void releasePointerCapture(View view) {
+            // no-op
+        }
+
+        @Override
+        public void setPointerIcon(View view, PointerIconCompat pointerIcon) {
+            // no-op
+        }
     }
 
     static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -1318,8 +1358,7 @@
         @Override
         public ViewPropertyAnimatorCompat animate(View view) {
             if (mViewPropertyAnimatorCompatMap == null) {
-                mViewPropertyAnimatorCompatMap =
-                        new WeakHashMap<View, ViewPropertyAnimatorCompat>();
+                mViewPropertyAnimatorCompatMap = new WeakHashMap<>();
             }
             ViewPropertyAnimatorCompat vpa = mViewPropertyAnimatorCompatMap.get(view);
             if (vpa == null) {
@@ -1677,10 +1716,34 @@
         }
     }
 
+    static class Api24ViewCompatImpl extends MarshmallowViewCompatImpl {
+        @Override
+        public void setPointerCapture(View view) {
+            ViewCompatApi24.setPointerCapture(view);
+        }
+
+        @Override
+        public boolean hasPointerCapture(View view) {
+            return ViewCompatApi24.hasPointerCapture(view);
+        }
+
+        @Override
+        public void releasePointerCapture(View view) {
+            ViewCompatApi24.releasePointerCapture(view);
+        }
+
+        @Override
+        public void setPointerIcon(View view, PointerIconCompat pointerIconCompat) {
+            ViewCompatApi24.setPointerIcon(view, pointerIconCompat.getPointerIcon());
+        }
+    }
+
     static final ViewCompatImpl IMPL;
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
-        if (version >= 23) {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new Api24ViewCompatImpl();
+        } else if (version >= 23) {
             IMPL = new MarshmallowViewCompatImpl();
         } else if (version >= 21) {
             IMPL = new LollipopViewCompatImpl();
@@ -1738,6 +1801,7 @@
      */
     @OverScroll
     public static int getOverScrollMode(View v) {
+        //noinspection ResourceType
         return IMPL.getOverScrollMode(v);
     }
 
@@ -1774,10 +1838,9 @@
      *     event.getText().add(selectedDateUtterance);
      * }</pre>
      * <p>
-     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
-     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
-     * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
-     *  AccessibilityEvent)}
+     * If an {@link AccessibilityDelegateCompat} has been specified via calling
+     * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
+     * {@link AccessibilityDelegateCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
      * is responsible for handling this call.
      * </p>
      * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
@@ -1807,15 +1870,10 @@
      *     event.setPassword(true);
      * }</pre>
      * <p>
-     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
-     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
-     * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
-     *  AccessibilityEvent)}
+     * If an {@link AccessibilityDelegateCompat} has been specified via calling
+     * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+     * {@link AccessibilityDelegateCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
      * is responsible for handling this call.
-     * </p>
-     * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
-     * information to the event, in case the default implementation has basic information to add.
-     * </p>
      *
      * @param v The View against which to invoke the method.
      * @param event The event to initialize.
@@ -1828,33 +1886,27 @@
     }
 
     /**
-     * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+     * Initializes an {@link AccessibilityNodeInfoCompat} with information
      * about this view. The base implementation sets:
      * <ul>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li>
-     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setParent(View)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setPackageName(CharSequence)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setClassName(CharSequence)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setEnabled(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setClickable(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setFocusable(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setFocused(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setLongClickable(boolean)},</li>
+     * <li>{@link AccessibilityNodeInfoCompat#setSelected(boolean)},</li>
      * </ul>
      * <p>
-     * Subclasses should override this method, call the super implementation,
-     * and set additional attributes.
-     * </p>
-     * <p>
-     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
-     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
-     * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
-     *  android.view.accessibility.AccessibilityNodeInfo)}
-     * is responsible for handling this call.
-     * </p>
+     * If an {@link AccessibilityDelegateCompat} has been specified via calling
+     * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+     * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)}
+     * method is responsible for handling this call.
      *
      * @param v The View against which to invoke the method.
      * @param info The instance to initialize.
@@ -1864,15 +1916,27 @@
     }
 
     /**
-     * Sets a delegate for implementing accessibility support via compositon as
-     * opposed to inheritance. The delegate's primary use is for implementing
-     * backwards compatible widgets. For more details see
-     * {@link android.view.View.AccessibilityDelegate}.
+     * Sets a delegate for implementing accessibility support via composition
+     * (as opposed to inheritance). For more details, see
+     * {@link AccessibilityDelegateCompat}.
+     * <p>
+     * On platform versions prior to API 14, this method is a no-op.
+     * <p>
+     * <strong>Note:</strong> On platform versions prior to
+     * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+     * views in the {@code android.widget.*} package are called <i>before</i>
+     * host methods. This prevents certain properties such as class name from
+     * being modified by overriding
+     * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+     * as any changes will be overwritten by the host class.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+     * methods are called <i>after</i> host methods, which all properties to be
+     * modified without being overwritten by the host class.
      *
-     * @param v The View against which to invoke the method.
-     * @param delegate The delegate instance.
-     *
-     * @see android.view.View.AccessibilityDelegate
+     * @param delegate the object to which accessibility method calls should be
+     *                 delegated
+     * @see AccessibilityDelegateCompat
      */
     public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
         IMPL.setAccessibilityDelegate(v, delegate);
@@ -1988,6 +2052,7 @@
      */
     @ImportantForAccessibility
     public static int getImportantForAccessibility(View view) {
+        //noinspection ResourceType
         return IMPL.getImportantForAccessibility(view);
     }
 
@@ -2095,13 +2160,6 @@
      * {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
      * for more information on when and how to use layers.</p>
      *
-     * @param layerType The ype of layer to use with this view, must be one of
-     *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
-     *        {@link #LAYER_TYPE_HARDWARE}
-     * @param paint The paint used to compose the layer. This argument is optional
-     *        and can be null. It is ignored when the layer type is
-     *        {@link #LAYER_TYPE_NONE}
-     *
      * @param view View to set the layer type for
      * @param layerType The type of layer to use with this view, must be one of
      *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
@@ -2132,6 +2190,7 @@
      */
     @LayerType
     public static int getLayerType(View view) {
+        //noinspection ResourceType
         return IMPL.getLayerType(view);
     }
 
@@ -2203,6 +2262,7 @@
      */
     @ResolvedLayoutDirectionMode
     public static int getLayoutDirection(View view) {
+        //noinspection ResourceType
         return IMPL.getLayoutDirection(view);
     }
 
@@ -2329,6 +2389,7 @@
      */
     @AccessibilityLiveRegion
     public static int getAccessibilityLiveRegion(View view) {
+        //noinspection ResourceType
         return IMPL.getAccessibilityLiveRegion(view);
     }
 
@@ -3301,5 +3362,55 @@
         return IMPL.getScrollIndicators(view);
     }
 
-    private ViewCompat() {}
+    /**
+     * Request capturing further mouse events.
+     *
+     * When the view captures, the mouse pointer icon will disappear and will not change its
+     * position. Further mouse events will come to the capturing view, and the mouse movements
+     * will can be detected through {@link MotionEventCompat#AXIS_RELATIVE_X} and
+     * {@link MotionEventCompat#AXIS_RELATIVE_Y}. Non-mouse events (touchscreen, or stylus) will
+     * not be affected.
+     *
+     * The capture will be released through {@link #releasePointerCapture(View)}, or will be lost
+     * automatically when the view or containing window disappear.
+     *
+     * @see #releasePointerCapture(View)
+     * @see #hasPointerCapture(View)
+     */
+    public static void setPointerCapture(@NonNull View view) {
+        IMPL.setPointerCapture(view);
+    }
+
+    /**
+     * Checks the capture status of mouse events.
+     *
+     * @return true if the view has the capture.
+     * @see #setPointerCapture(View)
+     * @see #hasPointerCapture(View)
+     */
+    public static boolean hasPointerCapture(@NonNull View view) {
+        return IMPL.hasPointerCapture(view);
+    }
+
+    /**
+     * Release the current capture of mouse events.
+     *
+     * If the view does not have the capture, it will do nothing.
+     * @see #setPointerCapture(View)
+     * @see #hasPointerCapture(View)
+     */
+    public static void releasePointerCapture(@NonNull View view) {
+        IMPL.releasePointerCapture(view);
+    }
+
+
+    /**
+     * Set the pointer icon for the current view.
+     * @param pointerIcon A PointerIconCompat instance which will be shown when the mouse hovers.
+     */
+    public static void setPointerIcon(@NonNull View view, PointerIconCompat pointerIcon) {
+        IMPL.setPointerIcon(view, pointerIcon);
+    }
+
+    protected ViewCompat() {}
 }
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 2752ebc..b894ad8 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat;
 import android.support.v4.view.ViewCompat;
 import android.text.InputType;
@@ -611,6 +612,8 @@
         public boolean isMultiLine(Object info);
         public void setMultiLine(Object info, boolean multiLine);
         public boolean refresh(Object info);
+        public CharSequence getRoleDescription(Object info);
+        public void setRoleDescription(Object info, CharSequence roleDescription);
     }
 
     static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@@ -1215,6 +1218,15 @@
         public boolean refresh(Object info) {
             return false;
         }
+
+        @Override
+        public CharSequence getRoleDescription(Object info) {
+            return null;
+        }
+
+        @Override
+        public void setRoleDescription(Object info, CharSequence roleDescription) {
+        }
     }
 
     static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -1753,6 +1765,16 @@
         public void setMultiLine(Object info, boolean multiLine) {
             AccessibilityNodeInfoCompatKitKat.setMultiLine(info, multiLine);
         }
+
+        @Override
+        public CharSequence getRoleDescription(Object info) {
+            return AccessibilityNodeInfoCompatKitKat.getRoleDescription(info);
+        }
+
+        @Override
+        public void setRoleDescription(Object info, CharSequence roleDescription) {
+            AccessibilityNodeInfoCompatKitKat.setRoleDescription(info, roleDescription);
+        }
     }
 
     static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
@@ -3768,6 +3790,42 @@
         return IMPL.refresh(mInfo);
     }
 
+    /**
+     * Gets the custom role description.
+     * @return The role description.
+     */
+    public @Nullable CharSequence getRoleDescription() {
+        return IMPL.getRoleDescription(mInfo);
+    }
+
+    /**
+     * Sets the custom role description.
+     *
+     * <p>
+     *   The role description allows you to customize the name for the view's semantic
+     *   role. For example, if you create a custom subclass of {@link android.view.View}
+     *   to display a menu bar, you could assign it the role description of "menu bar".
+     * </p>
+     * <p>
+     *   <strong>Warning:</strong> For consistency with other applications, you should
+     *   not use the role description to force accessibility services to describe
+     *   standard views (such as buttons or checkboxes) using specific wording. For
+     *   example, you should not set a role description of "check box" or "tick box" for
+     *   a standard {@link android.widget.CheckBox}. Instead let accessibility services
+     *   decide what feedback to provide.
+     * </p>
+     * <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 roleDescription The role description.
+     */
+    public void setRoleDescription(@Nullable CharSequence roleDescription) {
+        IMPL.setRoleDescription(mInfo, roleDescription);
+    }
+
     @Override
     public int hashCode() {
         return (mInfo == null) ? 0 : mInfo.hashCode();
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index 64f6634..9666740 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -19,22 +19,29 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.SparseArrayCompat;
 import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.KeyEventCompat;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewCompat.FocusDirection;
+import android.support.v4.view.ViewCompat.FocusRealDirection;
 import android.support.v4.view.ViewParentCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityManagerCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -47,8 +54,36 @@
  * <p>
  * Clients should override abstract methods on this class and attach it to the
  * host view using {@link ViewCompat#setAccessibilityDelegate}:
- *
+ * <p>
  * <pre>
+ * class MyCustomView extends View {
+ *     private MyVirtualViewHelper mVirtualViewHelper;
+ *
+ *     public MyCustomView(Context context, ...) {
+ *         ...
+ *         mVirtualViewHelper = new MyVirtualViewHelper(this);
+ *         ViewCompat.setAccessibilityDelegate(this, mVirtualViewHelper);
+ *     }
+ *
+ *     &#64;Override
+ *     public boolean dispatchHoverEvent(MotionEvent event) {
+ *       return mHelper.dispatchHoverEvent(this, event)
+ *           || super.dispatchHoverEvent(event);
+ *     }
+ *
+ *     &#64;Override
+ *     public boolean dispatchKeyEvent(KeyEvent event) {
+ *       return mHelper.dispatchKeyEvent(event)
+ *           || super.dispatchKeyEvent(event);
+ *     }
+ *
+ *     &#64;Override
+ *     public boolean onFocusChanged(boolean gainFocus, int direction,
+ *         Rect previouslyFocusedRect) {
+ *       super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ *       mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ *     }
+ * }
  * mAccessHelper = new MyExploreByTouchHelper(someView);
  * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
  * </pre>
@@ -61,7 +96,7 @@
     public static final int HOST_ID = View.NO_ID;
 
     /** Default class name used for virtual views. */
-    private static final String DEFAULT_CLASS_NAME = View.class.getName();
+    private static final String DEFAULT_CLASS_NAME = "android.view.View";
 
     // Temporary, reusable data structures.
     private final Rect mTempScreenRect = new Rect();
@@ -69,70 +104,81 @@
     private final Rect mTempVisibleRect = new Rect();
     private final int[] mTempGlobalRect = new int[2];
 
+    /** Cache of accessibility nodes. This is populated on-demand. */
+    private final SparseArrayCompat<AccessibilityNodeInfoCompat> mCachedNodes =
+            new SparseArrayCompat<>();
+
     /** System accessibility manager, used to check state and send events. */
     private final AccessibilityManager mManager;
 
     /** View whose internal structure is exposed through this helper. */
-    private final View mView;
+    private final View mHost;
 
-    /** Node provider that handles creating nodes and performing actions. */
-    private ExploreByTouchNodeProvider mNodeProvider;
+    /** Virtual node provider used to expose logical structure to services. */
+    private MyNodeProvider mNodeProvider;
 
-    /** Virtual view id for the currently focused logical item. */
-    private int mFocusedVirtualViewId = INVALID_ID;
+    /** Identifier for the virtual view that holds accessibility focus. */
+    private int mAccessibilityFocusedVirtualViewId = INVALID_ID;
 
-    /** Virtual view id for the currently hovered logical item. */
+    /** Identifier for the virtual view that holds keyboard focus. */
+    private int mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+    /** Identifier for the virtual view that is currently hovered. */
     private int mHoveredVirtualViewId = INVALID_ID;
 
     /**
-     * Factory method to create a new {@link ExploreByTouchHelper}.
+     * Constructs a new helper that can expose a virtual view hierarchy for the
+     * specified host view.
      *
-     * @param forView View whose logical children are exposed by this helper.
+     * @param host view whose virtual view hierarchy is exposed by this helper
      */
-    public ExploreByTouchHelper(View forView) {
-        if (forView == null) {
+    public ExploreByTouchHelper(View host) {
+        if (host == null) {
             throw new IllegalArgumentException("View may not be null");
         }
 
-        mView = forView;
-        final Context context = forView.getContext();
+        mHost = host;
+
+        final Context context = host.getContext();
         mManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        // Host view must be focusable so that we can delegate to virtual
+        // views.
+        host.setFocusable(true);
+        if (ViewCompat.getImportantForAccessibility(host)
+                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            ViewCompat.setImportantForAccessibility(
+                    host, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
     }
 
-    /**
-     * Returns the {@link AccessibilityNodeProviderCompat} for this helper.
-     *
-     * @param host View whose logical children are exposed by this helper.
-     * @return The accessibility node provider for this helper.
-     */
     @Override
     public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
         if (mNodeProvider == null) {
-            mNodeProvider = new ExploreByTouchNodeProvider();
+            mNodeProvider = new MyNodeProvider();
         }
         return mNodeProvider;
     }
 
     /**
+     * Delegates hover events from the host view.
+     * <p>
      * Dispatches hover {@link MotionEvent}s to the virtual view hierarchy when
      * the Explore by Touch feature is enabled.
      * <p>
-     * This method should be called by overriding
-     * {@link View#dispatchHoverEvent}:
-     *
+     * This method should be called by overriding the host view's
+     * {@link View#dispatchHoverEvent(MotionEvent)} method:
      * <pre>&#64;Override
      * public boolean dispatchHoverEvent(MotionEvent event) {
-     *   if (mHelper.dispatchHoverEvent(this, event) {
-     *     return true;
-     *   }
-     *   return super.dispatchHoverEvent(event);
+     *   return mHelper.dispatchHoverEvent(this, event)
+     *       || super.dispatchHoverEvent(event);
      * }
      * </pre>
      *
      * @param event The hover event to dispatch to the virtual view hierarchy.
      * @return Whether the hover event was handled.
      */
-    public boolean dispatchHoverEvent(MotionEvent event) {
+    public final boolean dispatchHoverEvent(@NonNull MotionEvent event) {
         if (!mManager.isEnabled()
                 || !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
             return false;
@@ -145,7 +191,7 @@
                 updateHoveredVirtualView(virtualViewId);
                 return (virtualViewId != INVALID_ID);
             case MotionEventCompat.ACTION_HOVER_EXIT:
-                if (mFocusedVirtualViewId != INVALID_ID) {
+                if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
                     updateHoveredVirtualView(INVALID_ID);
                     return true;
                 }
@@ -156,78 +202,412 @@
     }
 
     /**
+     * Delegates key events from the host view.
+     * <p>
+     * This method should be called by overriding the host view's
+     * {@link View#dispatchKeyEvent(KeyEvent)} method:
+     * <pre>&#64;Override
+     * public boolean dispatchKeyEvent(KeyEvent event) {
+     *   return mHelper.dispatchKeyEvent(event)
+     *       || super.dispatchKeyEvent(event);
+     * }
+     * </pre>
+     */
+    public final boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+        boolean handled = false;
+
+        final int action = event.getAction();
+        if (action != KeyEvent.ACTION_UP) {
+            final int keyCode = event.getKeyCode();
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                    if (KeyEventCompat.hasNoModifiers(event)) {
+                        final int direction = keyToDirection(keyCode);
+                        final int count = 1 + event.getRepeatCount();
+                        for (int i = 0; i < count; i++) {
+                            if (moveFocus(direction, null)) {
+                                handled = true;
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_ENTER:
+                    if (KeyEventCompat.hasNoModifiers(event)) {
+                        if (event.getRepeatCount() == 0) {
+                            clickKeyboardFocusedVirtualView();
+                            handled = true;
+                        }
+                    }
+                    break;
+                case KeyEvent.KEYCODE_TAB:
+                    if (KeyEventCompat.hasNoModifiers(event)) {
+                        handled = moveFocus(View.FOCUS_FORWARD, null);
+                    } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
+                        handled = moveFocus(View.FOCUS_BACKWARD, null);
+                    }
+                    break;
+            }
+        }
+
+        return handled;
+    }
+
+    /**
+     * Delegates focus changes from the host view.
+     * <p>
+     * This method should be called by overriding the host view's
+     * {@link View#onFocusChanged(boolean, int, Rect)} method:
+     * <pre>&#64;Override
+     * public boolean onFocusChanged(boolean gainFocus, int direction,
+     *     Rect previouslyFocusedRect) {
+     *   super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+     *   mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+     * }
+     * </pre>
+     */
+    public final void onFocusChanged(boolean gainFocus, int direction,
+            @Nullable Rect previouslyFocusedRect) {
+        if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+            clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+        }
+
+        if (gainFocus) {
+            moveFocus(direction, previouslyFocusedRect);
+        }
+    }
+
+    /**
+     * @return the identifier of the virtual view that has accessibility focus
+     *         or {@link #INVALID_ID} if no virtual view has accessibility
+     *         focus
+     */
+    public final int getAccessibilityFocusedVirtualViewId() {
+        return mAccessibilityFocusedVirtualViewId;
+    }
+
+    /**
+     * @return the identifier of the virtual view that has keyboard focus
+     *         or {@link #INVALID_ID} if no virtual view has keyboard focus
+     */
+    public final int getKeyboardFocusedVirtualViewId() {
+        return mKeyboardFocusedVirtualViewId;
+    }
+
+    /**
+     * Maps key event codes to focus directions.
+     *
+     * @param keyCode the key event code
+     * @return the corresponding focus direction
+     */
+    @FocusRealDirection
+    private static int keyToDirection(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                return View.FOCUS_LEFT;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                return View.FOCUS_UP;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                return View.FOCUS_RIGHT;
+            default:
+                return View.FOCUS_DOWN;
+        }
+    }
+
+    /**
+     * Obtains the bounds for the specified virtual view.
+     *
+     * @param virtualViewId the identifier of the virtual view
+     * @param outBounds the rect to populate with virtual view bounds
+     */
+    private void getBoundsInParent(int virtualViewId, Rect outBounds) {
+        final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+        node.getBoundsInParent(outBounds);
+    }
+
+    /**
+     * Adapts AccessibilityNodeInfoCompat for obtaining bounds.
+     */
+    private static final FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat> NODE_ADAPTER =
+            new FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat>() {
+                @Override
+                public void obtainBounds(AccessibilityNodeInfoCompat node, Rect outBounds) {
+                    node.getBoundsInParent(outBounds);
+                }
+            };
+
+    /**
+     * Adapts SparseArrayCompat for iterating through values.
+     */
+    private static final FocusStrategy.CollectionAdapter<SparseArrayCompat<
+            AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat> SPARSE_VALUES_ADAPTER =
+            new FocusStrategy.CollectionAdapter<SparseArrayCompat<
+                    AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat>() {
+                @Override
+                public AccessibilityNodeInfoCompat get(
+                        SparseArrayCompat<AccessibilityNodeInfoCompat> collection, int index) {
+                    return collection.valueAt(index);
+                }
+
+                @Override
+                public int size(SparseArrayCompat<AccessibilityNodeInfoCompat> collection) {
+                    return collection.size();
+                }
+            };
+
+    /**
+     * Attempts to move keyboard focus in the specified direction.
+     *
+     * @param direction the direction in which to move keyboard focus
+     * @param previouslyFocusedRect the bounds of the previously focused item,
+     *                              or {@code null} if not available
+     * @return {@code true} if keyboard focus moved to a virtual view managed
+     *         by this helper, or {@code false} otherwise
+     */
+    private boolean moveFocus(@FocusDirection int direction, @Nullable Rect previouslyFocusedRect) {
+        final int focusedNodeId = mKeyboardFocusedVirtualViewId;
+        final AccessibilityNodeInfoCompat focusedNode =
+                focusedNodeId == INVALID_ID ? null : mCachedNodes.get(focusedNodeId);
+
+        final AccessibilityNodeInfoCompat nextFocusedNode;
+        switch (direction) {
+            case View.FOCUS_FORWARD:
+            case View.FOCUS_BACKWARD:
+                final boolean isLayoutRtl =
+                        ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
+                nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(mCachedNodes,
+                        SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
+                        false);
+                break;
+            case View.FOCUS_LEFT:
+            case View.FOCUS_UP:
+            case View.FOCUS_RIGHT:
+            case View.FOCUS_DOWN:
+                final Rect selectedRect = new Rect();
+                if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+                    // Focus is moving from a virtual view within the host.
+                    getBoundsInParent(mKeyboardFocusedVirtualViewId, selectedRect);
+                } else if (previouslyFocusedRect != null) {
+                    // Focus is moving from a real view outside the host.
+                    selectedRect.set(previouslyFocusedRect);
+                } else {
+                    // Focus is moving from... somewhere? Make a guess.
+                    // Usually this happens when another view was too lazy
+                    // to pass the previously focused rect (ex. ScrollView
+                    // when moving UP or DOWN).
+                    guessPreviouslyFocusedRect(mHost, direction, selectedRect);
+                }
+                nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(mCachedNodes,
+                        SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
+                break;
+            default:
+                throw new IllegalArgumentException("direction must be one of "
+                        + "{FOCUS_FORWARD, FOCUS_BACKWARD, FOCUS_UP, FOCUS_DOWN, "
+                        + "FOCUS_LEFT, FOCUS_RIGHT}.");
+        }
+
+        final int nextFocusedNodeId;
+        if (nextFocusedNode == null) {
+            nextFocusedNodeId = INVALID_ID;
+        } else {
+            final int index = mCachedNodes.indexOfValue(nextFocusedNode);
+            nextFocusedNodeId = mCachedNodes.keyAt(index);
+        }
+
+        return requestKeyboardFocusForVirtualView(nextFocusedNodeId);
+    }
+
+    /**
+     * Obtains a best guess for the previously focused rect for keyboard focus
+     * moving in the specified direction.
+     *
+     * @param host the view into which focus is moving
+     * @param direction the absolute direction in which focus is moving
+     * @param outBounds the rect to populate with the best-guess bounds for the
+     *                  previous focus rect
+     */
+    private static Rect guessPreviouslyFocusedRect(@NonNull View host,
+            @FocusRealDirection int direction, @NonNull Rect outBounds) {
+        final int w = host.getWidth();
+        final int h = host.getHeight();
+
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                outBounds.set(w, 0, w, h);
+                break;
+            case View.FOCUS_UP:
+                outBounds.set(0, h, w, h);
+                break;
+            case View.FOCUS_RIGHT:
+                outBounds.set(-1, 0, -1, h);
+                break;
+            case View.FOCUS_DOWN:
+                outBounds.set(0, -1, w, -1);
+                break;
+            default:
+                throw new IllegalArgumentException("direction must be one of "
+                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+        }
+
+        return outBounds;
+    }
+
+    /**
+     * Performs a click action on the keyboard focused virtual view, if any.
+     *
+     * @return {@code true} if the click action was performed successfully or
+     *         {@code false} otherwise
+     */
+    private boolean clickKeyboardFocusedVirtualView() {
+        return mKeyboardFocusedVirtualViewId != INVALID_ID && onPerformActionForVirtualView(
+                mKeyboardFocusedVirtualViewId, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
+    }
+
+    /**
      * Populates an event of the specified type with information about an item
      * and attempts to send it up through the view hierarchy.
      * <p>
      * You should call this method after performing a user action that normally
      * fires an accessibility event, such as clicking on an item.
-     *
+     * <p>
      * <pre>public void performItemClick(T item) {
      *   ...
      *   sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
      * }
      * </pre>
      *
-     * @param virtualViewId The virtual view id for which to send an event.
-     * @param eventType The type of event to send.
-     * @return true if the event was sent successfully.
+     * @param virtualViewId the identifier of the virtual view for which to
+     *                      send an event
+     * @param eventType the type of event to send
+     * @return {@code true} if the event was sent successfully, {@code false}
+     *         otherwise
      */
-    public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+    public final boolean sendEventForVirtualView(int virtualViewId, int eventType) {
         if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
             return false;
         }
 
-        final ViewParent parent = mView.getParent();
+        final ViewParent parent = mHost.getParent();
         if (parent == null) {
             return false;
         }
 
         final AccessibilityEvent event = createEvent(virtualViewId, eventType);
-        return ViewParentCompat.requestSendAccessibilityEvent(parent, mView, event);
+        return ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
     }
 
     /**
      * Notifies the accessibility framework that the properties of the parent
      * view have changed.
      * <p>
-     * You <b>must</b> call this method after adding or removing items from the
-     * parent view.
+     * You <strong>must</strong> call this method after adding or removing
+     * items from the parent view.
      */
-    public void invalidateRoot() {
-        invalidateVirtualView(HOST_ID);
+    public final void invalidateRoot() {
+        invalidateVirtualView(HOST_ID, AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE);
     }
 
     /**
      * Notifies the accessibility framework that the properties of a particular
      * item have changed.
      * <p>
-     * You <b>must</b> call this method after changing any of the properties set
-     * in {@link #onPopulateNodeForVirtualView}.
+     * You <strong>must</strong> call this method after changing any of the
+     * properties set in
+     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
      *
-     * @param virtualViewId The virtual view id to invalidate.
+     * @param virtualViewId the virtual view id to invalidate, or
+     *                      {@link #HOST_ID} to invalidate the root view
+     * @see #invalidateVirtualView(int, int)
      */
-    public void invalidateVirtualView(int virtualViewId) {
-        sendEventForVirtualView(
-                virtualViewId, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+    public final void invalidateVirtualView(int virtualViewId) {
+        invalidateVirtualView(virtualViewId,
+                AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
-     * Returns the virtual view id for the currently focused item,
+     * Notifies the accessibility framework that the properties of a particular
+     * item have changed.
+     * <p>
+     * You <strong>must</strong> call this method after changing any of the
+     * properties set in
+     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
      *
-     * @return A virtual view id, or {@link #INVALID_ID} if no item is
-     *         currently focused.
+     * @param virtualViewId the virtual view id to invalidate, or
+     *                      {@link #HOST_ID} to invalidate the root view
+     * @param changeTypes the bit mask of change types. May be {@code 0} for the
+     *                    default (undefined) change type or one or more of:
+     *         <ul>
+     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_SUBTREE}
+     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_TEXT}
+     *         <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_UNDEFINED}
+     *         </ul>
      */
+    public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
+        final SparseArrayCompat<AccessibilityNodeInfoCompat> cachedNodes = mCachedNodes;
+        if (virtualViewId == HOST_ID
+                && (changeTypes & AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+            for (int i = 0, count = cachedNodes.size(); i < count; i++) {
+                cachedNodes.valueAt(i).recycle();
+            }
+            cachedNodes.clear();
+        } else {
+            final int index = cachedNodes.indexOfKey(virtualViewId);
+            if (index >= 0) {
+                cachedNodes.valueAt(index).recycle();
+                cachedNodes.removeAt(index);
+            }
+        }
+
+        if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
+            final ViewParent parent = mHost.getParent();
+            if (parent != null) {
+                // Send events up the hierarchy so they can be coalesced.
+                final AccessibilityEvent event = createEvent(virtualViewId,
+                        AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+                AccessibilityEventCompat.setContentChangeTypes(event, changeTypes);
+                ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
+            }
+        }
+    }
+
+    /**
+     * Returns the virtual view ID for the currently accessibility focused
+     * item.
+     *
+     * @return the identifier of the virtual view that has accessibility focus
+     *         or {@link #INVALID_ID} if no virtual view has accessibility
+     *         focus
+     * @deprecated Use {@link #getAccessibilityFocusedVirtualViewId()}.
+     */
+    @Deprecated
     public int getFocusedVirtualView() {
-        return mFocusedVirtualViewId;
+        return getAccessibilityFocusedVirtualViewId();
+    }
+
+    /**
+     * Called when the focus state of a virtual view changes.
+     *
+     * @param virtualViewId the virtual view identifier
+     * @param hasFocus      {@code true} if the view has focus, {@code false}
+     *                      otherwise
+     */
+    protected void onVirtualViewKeyboardFocusChanged(int virtualViewId, boolean hasFocus) {
+        // Stub method.
     }
 
     /**
      * Sets the currently hovered item, sending hover accessibility events as
      * necessary to maintain the correct state.
      *
-     * @param virtualViewId The virtual view id for the item currently being
-     *            hovered, or {@link #INVALID_ID} if no item is hovered within
-     *            the parent view.
+     * @param virtualViewId the virtual view id for the item currently being
+     *                      hovered, or {@link #INVALID_ID} if no item is
+     *                      hovered within the parent view
      */
     private void updateHoveredVirtualView(int virtualViewId) {
         if (mHoveredVirtualViewId == virtualViewId) {
@@ -248,11 +628,11 @@
      * Constructs and returns an {@link AccessibilityEvent} for the specified
      * virtual view id, which includes the host view ({@link #HOST_ID}).
      *
-     * @param virtualViewId The virtual view id for the item for which to
-     *            construct an event.
-     * @param eventType The type of event to construct.
-     * @return An {@link AccessibilityEvent} populated with information about
-     *         the specified item.
+     * @param virtualViewId the virtual view id for the item for which to
+     *                      construct an event
+     * @param eventType the type of event to construct
+     * @return an {@link AccessibilityEvent} populated with information about
+     *         the specified item
      */
     private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
         switch (virtualViewId) {
@@ -266,30 +646,46 @@
     /**
      * Constructs and returns an {@link AccessibilityEvent} for the host node.
      *
-     * @param eventType The type of event to construct.
-     * @return An {@link AccessibilityEvent} populated with information about
-     *         the specified item.
+     * @param eventType the type of event to construct
+     * @return an {@link AccessibilityEvent} populated with information about
+     *         the specified item
      */
     private AccessibilityEvent createEventForHost(int eventType) {
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        ViewCompat.onInitializeAccessibilityEvent(mView, event);
+        ViewCompat.onInitializeAccessibilityEvent(mHost, event);
         return event;
     }
 
+    @Override
+    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(host, event);
+
+        // Allow the client to populate the event.
+        onPopulateEventForHost(event);
+    }
+
     /**
      * Constructs and returns an {@link AccessibilityEvent} populated with
      * information about the specified item.
      *
-     * @param virtualViewId The virtual view id for the item for which to
-     *            construct an event.
-     * @param eventType The type of event to construct.
-     * @return An {@link AccessibilityEvent} populated with information about
-     *         the specified item.
+     * @param virtualViewId the virtual view id for the item for which to
+     *                      construct an event
+     * @param eventType the type of event to construct
+     * @return an {@link AccessibilityEvent} populated with information about
+     *         the specified item
      */
     private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-        event.setEnabled(true);
-        event.setClassName(DEFAULT_CLASS_NAME);
+        final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
+        final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+
+        // Allow the client to override these properties,
+        record.getText().add(node.getText());
+        record.setContentDescription(node.getContentDescription());
+        record.setScrollable(node.isScrollable());
+        record.setPassword(node.isPassword());
+        record.setEnabled(node.isEnabled());
+        record.setChecked(node.isChecked());
 
         // Allow the client to populate the event.
         onPopulateEventForVirtualView(virtualViewId, event);
@@ -301,55 +697,75 @@
         }
 
         // Don't allow the client to override these properties.
-        event.setPackageName(mView.getContext().getPackageName());
-
-        final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
-        record.setSource(mView, virtualViewId);
+        record.setClassName(node.getClassName());
+        record.setSource(mHost, virtualViewId);
+        event.setPackageName(mHost.getContext().getPackageName());
 
         return event;
     }
 
     /**
-     * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
-     * specified virtual view id, which includes the host view
-     * ({@link #HOST_ID}).
+     * Obtains a populated {@link AccessibilityNodeInfoCompat} for the
+     * virtual view with the specified identifier.
+     * <p>
+     * This method may be called with identifier {@link #HOST_ID} to obtain a
+     * node for the host view.
      *
-     * @param virtualViewId The virtual view id for the item for which to
-     *            construct a node.
-     * @return An {@link AccessibilityNodeInfoCompat} populated with information
-     *         about the specified item.
+     * @param virtualViewId the identifier of the virtual view for which to
+     *                      construct a node
+     * @return an {@link AccessibilityNodeInfoCompat} populated with information
+     *         about the specified item
      */
-    private AccessibilityNodeInfoCompat createNode(int virtualViewId) {
-        switch (virtualViewId) {
-            case HOST_ID:
-                return createNodeForHost();
-            default:
-                return createNodeForChild(virtualViewId);
+    @NonNull
+    private AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
+        final AccessibilityNodeInfoCompat node;
+        final int cacheIndex = mCachedNodes.indexOfKey(virtualViewId);
+        if (cacheIndex >= 0) {
+            node = mCachedNodes.valueAt(cacheIndex);
+        } else if (virtualViewId == HOST_ID) {
+            node = createNodeForHost();
+        } else {
+            node = createNodeForChild(virtualViewId);
         }
+
+        mCachedNodes.put(virtualViewId, node);
+
+        return node;
     }
 
     /**
      * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
      * host view populated with its virtual descendants.
      *
-     * @return An {@link AccessibilityNodeInfoCompat} for the parent node.
+     * @return an {@link AccessibilityNodeInfoCompat} for the parent node
      */
+    @NonNull
     private AccessibilityNodeInfoCompat createNodeForHost() {
-        final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(mView);
-        ViewCompat.onInitializeAccessibilityNodeInfo(mView, node);
-
-        // Allow the client to populate the host node.
-        onPopulateNodeForHost(node);
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(mHost);
+        ViewCompat.onInitializeAccessibilityNodeInfo(mHost, info);
 
         // Add the virtual descendants.
-        final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+        final ArrayList<Integer> virtualViewIds = new ArrayList<>();
         getVisibleVirtualViews(virtualViewIds);
 
-        for (Integer childVirtualViewId : virtualViewIds) {
-            node.addChild(mView, childVirtualViewId);
+        final int realNodeCount = info.getChildCount();
+        if (realNodeCount > 0 && virtualViewIds.size() > 0) {
+            throw new RuntimeException("Views cannot have both real and virtual children");
         }
 
-        return node;
+        for (int i = 0, count = virtualViewIds.size(); i < count; i++) {
+            info.addChild(mHost, virtualViewIds.get(i));
+        }
+
+        return info;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+
+        // Allow the client to populate the host node.
+        onPopulateNodeForHost(info);
     }
 
     /**
@@ -376,15 +792,17 @@
      * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent}
      * </ul>
      *
-     * @param virtualViewId The virtual view id for item for which to construct
-     *            a node.
-     * @return An {@link AccessibilityNodeInfoCompat} for the specified item.
+     * @param virtualViewId the virtual view id for item for which to construct
+     *                      a node
+     * @return an {@link AccessibilityNodeInfoCompat} for the specified item
      */
+    @NonNull
     private AccessibilityNodeInfoCompat createNodeForChild(int virtualViewId) {
         final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain();
 
         // Ensure the client has good defaults.
         node.setEnabled(true);
+        node.setFocusable(true);
         node.setClassName(DEFAULT_CLASS_NAME);
 
         // Allow the client to populate the node.
@@ -413,12 +831,12 @@
         }
 
         // Don't allow the client to override these properties.
-        node.setPackageName(mView.getContext().getPackageName());
-        node.setSource(mView, virtualViewId);
-        node.setParent(mView);
+        node.setPackageName(mHost.getContext().getPackageName());
+        node.setSource(mHost, virtualViewId);
+        node.setParent(mHost);
 
         // Manage internal accessibility focus state.
-        if (mFocusedVirtualViewId == virtualViewId) {
+        if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
             node.setAccessibilityFocused(true);
             node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
         } else {
@@ -426,6 +844,15 @@
             node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
         }
 
+        // Manage internal keyboard focus state.
+        final boolean isFocused = mKeyboardFocusedVirtualViewId == virtualViewId;
+        if (isFocused) {
+            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS);
+        } else if (node.isFocusable()) {
+            node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
+        }
+        node.setFocused(isFocused);
+
         // Set the visibility based on the parent bound.
         if (intersectVisibleToUser(mTempParentRect)) {
             node.setVisibleToUser(true);
@@ -433,7 +860,7 @@
         }
 
         // Calculate screen-relative bound.
-        mView.getLocationOnScreen(mTempGlobalRect);
+        mHost.getLocationOnScreen(mTempGlobalRect);
         final int offsetX = mTempGlobalRect[0];
         final int offsetY = mTempGlobalRect[1];
         mTempScreenRect.set(mTempParentRect);
@@ -453,27 +880,21 @@
     }
 
     private boolean performActionForHost(int action, Bundle arguments) {
-        return ViewCompat.performAccessibilityAction(mView, action, arguments);
+        return ViewCompat.performAccessibilityAction(mHost, action, arguments);
     }
 
     private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
         switch (action) {
             case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
-            case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
-                return manageFocusForChild(virtualViewId, action, arguments);
-            default:
-                return onPerformActionForVirtualView(virtualViewId, action, arguments);
-        }
-    }
-
-    private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
-        switch (action) {
-            case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
                 return requestAccessibilityFocus(virtualViewId);
             case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
                 return clearAccessibilityFocus(virtualViewId);
+            case AccessibilityNodeInfoCompat.ACTION_FOCUS:
+                return requestKeyboardFocusForVirtualView(virtualViewId);
+            case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:
+                return clearKeyboardFocusForVirtualView(virtualViewId);
             default:
-                return false;
+                return onPerformActionForVirtualView(virtualViewId, action, arguments);
         }
     }
 
@@ -482,8 +903,8 @@
      * portion of its parent {@link View}. Modifies {@code localRect} to contain
      * only the visible portion.
      *
-     * @param localRect A rectangle in local (parent) coordinates.
-     * @return Whether the specified {@link Rect} is visible on the screen.
+     * @param localRect a rectangle in local (parent) coordinates
+     * @return whether the specified {@link Rect} is visible on the screen
      */
     private boolean intersectVisibleToUser(Rect localRect) {
         // Missing or empty bounds mean this view is not visible.
@@ -492,12 +913,12 @@
         }
 
         // Attached to invisible window means this view is not visible.
-        if (mView.getWindowVisibility() != View.VISIBLE) {
+        if (mHost.getWindowVisibility() != View.VISIBLE) {
             return false;
         }
 
         // An invisible predecessor means that this view is not visible.
-        ViewParent viewParent = mView.getParent();
+        ViewParent viewParent = mHost.getParent();
         while (viewParent instanceof View) {
             final View view = (View) viewParent;
             if ((ViewCompat.getAlpha(view) <= 0) || (view.getVisibility() != View.VISIBLE)) {
@@ -512,7 +933,7 @@
         }
 
         // If no portion of the parent is visible, this view is not visible.
-        if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+        if (!mHost.getLocalVisibleRect(mTempVisibleRect)) {
             return false;
         }
 
@@ -521,15 +942,6 @@
     }
 
     /**
-     * Returns whether this virtual view is accessibility focused.
-     *
-     * @return True if the view is accessibility focused.
-     */
-    private boolean isAccessibilityFocused(int virtualViewId) {
-        return (mFocusedVirtualViewId == virtualViewId);
-    }
-
-    /**
      * Attempts to give accessibility focus to a virtual view.
      * <p>
      * A virtual view will not actually take focus if
@@ -537,9 +949,9 @@
      * {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
      * or the view already has accessibility focus.
      *
-     * @param virtualViewId The id of the virtual view on which to place
-     *            accessibility focus.
-     * @return Whether this virtual view actually took accessibility focus.
+     * @param virtualViewId the identifier of the virtual view on which to
+     *                      place accessibility focus
+     * @return whether this virtual view actually took accessibility focus
      */
     private boolean requestAccessibilityFocus(int virtualViewId) {
         if (!mManager.isEnabled()
@@ -547,18 +959,17 @@
             return false;
         }
         // TODO: Check virtual view visibility.
-        if (!isAccessibilityFocused(virtualViewId)) {
+        if (mAccessibilityFocusedVirtualViewId != virtualViewId) {
             // Clear focus from the previously focused view, if applicable.
-            if (mFocusedVirtualViewId != INVALID_ID) {
-                sendEventForVirtualView(mFocusedVirtualViewId,
-                        AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+            if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
+                clearAccessibilityFocus(mAccessibilityFocusedVirtualViewId);
             }
 
             // Set focus on the new view.
-            mFocusedVirtualViewId = virtualViewId;
+            mAccessibilityFocusedVirtualViewId = virtualViewId;
 
             // TODO: Only invalidate virtual view bounds.
-            mView.invalidate();
+            mHost.invalidate();
             sendEventForVirtualView(virtualViewId,
                     AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
             return true;
@@ -569,14 +980,14 @@
     /**
      * Attempts to clear accessibility focus from a virtual view.
      *
-     * @param virtualViewId The id of the virtual view from which to clear
-     *            accessibility focus.
-     * @return Whether this virtual view actually cleared accessibility focus.
+     * @param virtualViewId the identifier of the virtual view from which to
+     *                      clear accessibility focus
+     * @return whether this virtual view actually cleared accessibility focus
      */
     private boolean clearAccessibilityFocus(int virtualViewId) {
-        if (isAccessibilityFocused(virtualViewId)) {
-            mFocusedVirtualViewId = INVALID_ID;
-            mView.invalidate();
+        if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
+            mAccessibilityFocusedVirtualViewId = INVALID_ID;
+            mHost.invalidate();
             sendEventForVirtualView(virtualViewId,
                     AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
             return true;
@@ -585,6 +996,57 @@
     }
 
     /**
+     * Attempts to give keyboard focus to a virtual view.
+     *
+     * @param virtualViewId the identifier of the virtual view on which to
+     *                      place keyboard focus
+     * @return whether this virtual view actually took keyboard focus
+     */
+    public final boolean requestKeyboardFocusForVirtualView(int virtualViewId) {
+        if (!mHost.isFocused() && !mHost.requestFocus()) {
+            // Host must have real keyboard focus.
+            return false;
+        }
+
+        if (mKeyboardFocusedVirtualViewId == virtualViewId) {
+            // The virtual view already has focus.
+            return false;
+        }
+
+        if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+            clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+        }
+
+        mKeyboardFocusedVirtualViewId = virtualViewId;
+
+        onVirtualViewKeyboardFocusChanged(virtualViewId, true);
+        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+        return true;
+    }
+
+    /**
+     * Attempts to clear keyboard focus from a virtual view.
+     *
+     * @param virtualViewId the identifier of the virtual view from which to
+     *                      clear keyboard focus
+     * @return whether this virtual view actually cleared keyboard focus
+     */
+    public final boolean clearKeyboardFocusForVirtualView(int virtualViewId) {
+        if (mKeyboardFocusedVirtualViewId != virtualViewId) {
+            // The virtual view is not focused.
+            return false;
+        }
+
+        mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+        onVirtualViewKeyboardFocusChanged(virtualViewId, false);
+        sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+        return true;
+    }
+
+    /**
      * Provides a mapping between view-relative coordinates and logical
      * items.
      *
@@ -609,22 +1071,25 @@
      * Populates an {@link AccessibilityEvent} with information about the
      * specified item.
      * <p>
-     * Implementations <b>must</b> populate the following required fields:
+     * The helper class automatically populates the following fields based on
+     * the values set by
+     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)},
+     * but implementations may optionally override them:
      * <ul>
-     * <li>event text, see {@link AccessibilityEvent#getText} or
-     * {@link AccessibilityEvent#setContentDescription}
-     * </ul>
-     * <p>
-     * The helper class automatically populates the following fields with
-     * default values, but implementations may optionally override them:
-     * <ul>
-     * <li>item class name, set to android.view.View, see
-     * {@link AccessibilityEvent#setClassName}
+     * <li>event text, see {@link AccessibilityEvent#getText()}
+     * <li>content description, see
+     * {@link AccessibilityEvent#setContentDescription(CharSequence)}
+     * <li>scrollability, see {@link AccessibilityEvent#setScrollable(boolean)}
+     * <li>password state, see {@link AccessibilityEvent#setPassword(boolean)}
+     * <li>enabled state, see {@link AccessibilityEvent#setEnabled(boolean)}
+     * <li>checked state, see {@link AccessibilityEvent#setChecked(boolean)}
      * </ul>
      * <p>
      * The following required fields are automatically populated by the
      * helper class and may not be overridden:
      * <ul>
+     * <li>item class name, set to the value used in
+     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}
      * <li>package name, set to the package of the host view's
      * {@link Context}, see {@link AccessibilityEvent#setPackageName}
      * <li>event source, set to the host view and virtual view identifier,
@@ -635,55 +1100,76 @@
      *            populate the event
      * @param event The event to populate
      */
-    protected abstract void onPopulateEventForVirtualView(
-            int virtualViewId, AccessibilityEvent event);
+    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+        // Default implementation is no-op.
+    }
+
+    /**
+     * Populates an {@link AccessibilityEvent} with information about the host
+     * view.
+     * <p>
+     * The default implementation is a no-op.
+     *
+     * @param event the event to populate with information about the host view
+     */
+    protected void onPopulateEventForHost(AccessibilityEvent event) {
+        // Default implementation is no-op.
+    }
 
     /**
      * Populates an {@link AccessibilityNodeInfoCompat} with information
      * about the specified item.
      * <p>
-     * Implementations <b>must</b> populate the following required fields:
+     * Implementations <strong>must</strong> populate the following required
+     * fields:
      * <ul>
-     * <li>event text, see {@link AccessibilityNodeInfoCompat#setText} or
-     * {@link AccessibilityNodeInfoCompat#setContentDescription}
+     * <li>event text, see
+     * {@link AccessibilityNodeInfoCompat#setText(CharSequence)} or
+     * {@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)}
      * <li>bounds in parent coordinates, see
-     * {@link AccessibilityNodeInfoCompat#setBoundsInParent}
+     * {@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)}
      * </ul>
      * <p>
      * The helper class automatically populates the following fields with
      * default values, but implementations may optionally override them:
      * <ul>
-     * <li>enabled state, set to true, see
-     * {@link AccessibilityNodeInfoCompat#setEnabled}
-     * <li>item class name, identical to the class name set by
-     * {@link #onPopulateEventForVirtualView}, see
-     * {@link AccessibilityNodeInfoCompat#setClassName}
+     * <li>enabled state, set to {@code true}, see
+     * {@link AccessibilityNodeInfoCompat#setEnabled(boolean)}
+     * <li>keyboard focusability, set to {@code true}, see
+     * {@link AccessibilityNodeInfoCompat#setFocusable(boolean)}
+     * <li>item class name, set to {@code android.view.View}, see
+     * {@link AccessibilityNodeInfoCompat#setClassName(CharSequence)}
      * </ul>
      * <p>
      * The following required fields are automatically populated by the
      * helper class and may not be overridden:
      * <ul>
      * <li>package name, identical to the package name set by
-     * {@link #onPopulateEventForVirtualView}, see
+     * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
      * {@link AccessibilityNodeInfoCompat#setPackageName}
      * <li>node source, identical to the event source set in
-     * {@link #onPopulateEventForVirtualView}, see
+     * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
      * {@link AccessibilityNodeInfoCompat#setSource(View, int)}
      * <li>parent view, set to the host view, see
      * {@link AccessibilityNodeInfoCompat#setParent(View)}
      * <li>visibility, computed based on parent-relative bounds, see
-     * {@link AccessibilityNodeInfoCompat#setVisibleToUser}
+     * {@link AccessibilityNodeInfoCompat#setVisibleToUser(boolean)}
      * <li>accessibility focus, computed based on internal helper state, see
-     * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused}
+     * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused(boolean)}
+     * <li>keyboard focus, computed based on internal helper state, see
+     * {@link AccessibilityNodeInfoCompat#setFocused(boolean)}
      * <li>bounds in screen coordinates, computed based on host view bounds,
-     * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen}
+     * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)}
      * </ul>
      * <p>
-     * Additionally, the helper class automatically handles accessibility
-     * focus management by adding the appropriate
-     * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} or
+     * Additionally, the helper class automatically handles keyboard focus and
+     * accessibility focus management by adding the appropriate
+     * {@link AccessibilityNodeInfoCompat#ACTION_FOCUS},
+     * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_FOCUS},
+     * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}, or
      * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
-     * action. Implementations must <b>never</b> manually add these actions.
+     * actions. Implementations must <strong>never</strong> manually add these
+     * actions.
      * <p>
      * The helper class also automatically modifies parent- and
      * screen-relative bounds to reflect the portion of the item visible
@@ -700,10 +1186,11 @@
      * Populates an {@link AccessibilityNodeInfoCompat} with information
      * about the host view.
      * <p>
-     * The following required fields are automatically populated by the
-     * helper class and may not be overridden:
+     * The default implementation is a no-op.
+     *
+     * @param node the node to populate with information about the host view
      */
-    public void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+    protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
         // Default implementation is no-op.
     }
 
@@ -713,8 +1200,9 @@
      * {@link AccessibilityNodeInfoCompat#performAction(int, Bundle)} for
      * more information.
      * <p>
-     * Implementations <b>must</b> handle any actions added manually in
-     * {@link #onPopulateNodeForVirtualView}.
+     * Implementations <strong>must</strong> handle any actions added manually
+     * in
+     * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
      * <p>
      * The helper class automatically handles focus management resulting
      * from {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}
@@ -733,13 +1221,16 @@
             int virtualViewId, int action, Bundle arguments);
 
     /**
-     * Exposes a virtual view hierarchy to the accessibility framework. Only
-     * used in API 16+.
+     * Exposes a virtual view hierarchy to the accessibility framework.
      */
-    private class ExploreByTouchNodeProvider extends AccessibilityNodeProviderCompat {
+    private class MyNodeProvider extends AccessibilityNodeProviderCompat {
         @Override
         public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
-            return ExploreByTouchHelper.this.createNode(virtualViewId);
+            // The caller takes ownership of the node and is expected to
+            // recycle it when done, so always return a copy.
+            final AccessibilityNodeInfoCompat node =
+                    ExploreByTouchHelper.this.obtainAccessibilityNodeInfo(virtualViewId);
+            return AccessibilityNodeInfoCompat.obtain(node);
         }
 
         @Override
diff --git a/v4/java/android/support/v4/widget/FocusStrategy.java b/v4/java/android/support/v4/widget/FocusStrategy.java
new file mode 100644
index 0000000..8be9f1a
--- /dev/null
+++ b/v4/java/android/support/v4/widget/FocusStrategy.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+
+import android.support.v4.view.ViewCompat.FocusRealDirection;
+import android.support.v4.view.ViewCompat.FocusRelativeDirection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Implements absolute and relative focus movement strategies. Adapted from
+ * android.view.FocusFinder to work with generic collections of bounded items.
+ */
+class FocusStrategy {
+    public static <L,T> T findNextFocusInRelativeDirection(@NonNull L focusables,
+            @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+            @Nullable T focused, @FocusRelativeDirection int direction, boolean isLayoutRtl,
+            boolean wrap) {
+        final int count = collectionAdapter.size(focusables);
+        final ArrayList<T> sortedFocusables = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            sortedFocusables.add(collectionAdapter.get(focusables, i));
+        }
+
+        final SequentialComparator<T> comparator = new SequentialComparator<>(isLayoutRtl, adapter);
+        Collections.sort(sortedFocusables, comparator);
+
+        switch (direction) {
+            case View.FOCUS_FORWARD:
+                return getNextFocusable(focused, sortedFocusables, wrap);
+            case View.FOCUS_BACKWARD:
+                return getPreviousFocusable(focused, sortedFocusables, wrap);
+            default:
+                throw new IllegalArgumentException("direction must be one of "
+                        + "{FOCUS_FORWARD, FOCUS_BACKWARD}.");
+        }
+    }
+
+    private static <T> T getNextFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+        final int count = focusables.size();
+
+        // The position of the next focusable item, which is the first item if
+        // no item is currently focused.
+        final int position = (focused == null ? -1 : focusables.lastIndexOf(focused)) + 1;
+        if (position < count) {
+            return focusables.get(position);
+        } else if (wrap && count > 0) {
+            return focusables.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    private static <T> T getPreviousFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+        final int count = focusables.size();
+
+        // The position of the previous focusable item, which is the last item
+        // if no item is currently focused.
+        final int position = (focused == null ? count : focusables.indexOf(focused)) - 1;
+        if (position >= 0) {
+            return focusables.get(position);
+        } else if (wrap && count > 0) {
+            return focusables.get(count - 1);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sorts views according to their visual layout and geometry for default tab order.
+     * This is used for sequential focus traversal.
+     */
+    private static class SequentialComparator<T> implements Comparator<T> {
+        private final Rect mTemp1 = new Rect();
+        private final Rect mTemp2 = new Rect();
+
+        private final boolean mIsLayoutRtl;
+        private final BoundsAdapter<T> mAdapter;
+
+        public SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) {
+            mIsLayoutRtl = isLayoutRtl;
+            mAdapter = adapter;
+        }
+
+        public int compare(T first, T second) {
+            final Rect firstRect = mTemp1;
+            final Rect secondRect = mTemp2;
+
+            mAdapter.obtainBounds(first, firstRect);
+            mAdapter.obtainBounds(second, secondRect);
+
+            if (firstRect.top < secondRect.top) {
+                return -1;
+            } else if (firstRect.top > secondRect.top) {
+                return 1;
+            } else if (firstRect.left < secondRect.left) {
+                return mIsLayoutRtl ? 1 : -1;
+            } else if (firstRect.left > secondRect.left) {
+                return mIsLayoutRtl ? -1 : 1;
+            } else if (firstRect.bottom < secondRect.bottom) {
+                return -1;
+            } else if (firstRect.bottom > secondRect.bottom) {
+                return 1;
+            } else if (firstRect.right < secondRect.right) {
+                return mIsLayoutRtl ? 1 : -1;
+            } else if (firstRect.right > secondRect.right) {
+                return mIsLayoutRtl ? -1 : 1;
+            } else {
+                // The view are distinct but completely coincident so we
+                // consider them equal for our purposes. Since the sort is
+                // stable, this means that the views will retain their
+                // layout order relative to one another.
+                return 0;
+            }
+        }
+    }
+
+    public static <L,T> T findNextFocusInAbsoluteDirection(@NonNull L focusables,
+            @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+            @Nullable T focused, @NonNull Rect focusedRect, int direction) {
+        // Initialize the best candidate to something impossible so that
+        // the first plausible view will become the best choice.
+        final Rect bestCandidateRect = new Rect(focusedRect);
+
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                bestCandidateRect.offset(focusedRect.width() + 1, 0);
+                break;
+            case View.FOCUS_RIGHT:
+                bestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+                break;
+            case View.FOCUS_UP:
+                bestCandidateRect.offset(0, focusedRect.height() + 1);
+                break;
+            case View.FOCUS_DOWN:
+                bestCandidateRect.offset(0, -(focusedRect.height() + 1));
+                break;
+            default:
+                throw new IllegalArgumentException("direction must be one of "
+                        + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+        }
+
+        T closest = null;
+
+        final int count = collectionAdapter.size(focusables);
+        final Rect focusableRect = new Rect();
+        for (int i = 0; i < count; i++) {
+            final T focusable = collectionAdapter.get(focusables, i);
+            if (focusable == focused) {
+                continue;
+            }
+
+            // get focus bounds of other view
+            adapter.obtainBounds(focusable, focusableRect);
+            if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) {
+                bestCandidateRect.set(focusableRect);
+                closest = focusable;
+            }
+        }
+
+        return closest;
+    }
+
+    /**
+     * Is candidate a better candidate than currentBest for a focus search
+     * in a particular direction from a source rect? This is the core
+     * routine that determines the order of focus searching.
+     *
+     * @param direction   the direction (up, down, left, right)
+     * @param source      the source from which we are searching
+     * @param candidate   the candidate rectangle
+     * @param currentBest the current best rectangle
+     * @return {@code true} if the candidate rectangle is a better than the
+     * current best rectangle, {@code false} otherwise
+     */
+    private static boolean isBetterCandidate(
+            @FocusRealDirection int direction, @NonNull Rect source,
+            @NonNull Rect candidate, @NonNull Rect currentBest) {
+        // To be a better candidate, need to at least be a candidate in the
+        // first place. :)
+        if (!isCandidate(source, candidate, direction)) {
+            return false;
+        }
+
+        // We know that candidateRect is a candidate. If currentBest is not
+        // a candidate, candidateRect is better.
+        if (!isCandidate(source, currentBest, direction)) {
+            return true;
+        }
+
+        // If candidateRect is better by beam, it wins.
+        if (beamBeats(direction, source, candidate, currentBest)) {
+            return true;
+        }
+
+        // If currentBest is better, then candidateRect cant' be. :)
+        if (beamBeats(direction, source, currentBest, candidate)) {
+            return false;
+        }
+
+        // Otherwise, do fudge-tastic comparison of the major and minor
+        // axis.
+        final int candidateDist = getWeightedDistanceFor(
+                majorAxisDistance(direction, source, candidate),
+                minorAxisDistance(direction, source, candidate));
+        final int currentBestDist = getWeightedDistanceFor(
+                majorAxisDistance(direction, source, currentBest),
+                minorAxisDistance(direction, source, currentBest));
+        return candidateDist < currentBestDist;
+    }
+
+    /**
+     * One rectangle may be another candidate than another by virtue of
+     * being exclusively in the beam of the source rect.
+     *
+     * @return whether rect1 is a better candidate than rect2 by virtue of
+     * it being in source's beam
+     */
+    private static boolean beamBeats(@FocusRealDirection int direction,
+            @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) {
+        final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+        final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+        // If rect1 isn't exclusively in the src beam, it doesn't win.
+        if (rect2InSrcBeam || !rect1InSrcBeam) {
+            return false;
+        }
+
+        // We know rect1 is in the beam, and rect2 is not.
+
+        // If rect1 is to the direction of, and rect2 is not, rect1 wins.
+        // For example, for direction left, if rect1 is to the left of the
+        // source and rect2 is below, then we always prefer the in beam
+        // rect1, since rect2 could be reached by going down.
+        if (!isToDirectionOf(direction, source, rect2)) {
+            return true;
+        }
+
+        // For horizontal directions, being exclusively in beam always
+        // wins.
+        if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
+            return true;
+        }
+
+        // For vertical directions, beams only beat up to a point: now, as
+        // long as rect2 isn't completely closer, rect1 wins, e.g. for
+        // direction down, completely closer means for rect2's top edge to
+        // be closer to the source's top edge than rect1's bottom edge.
+        return majorAxisDistance(direction, source, rect1)
+                < majorAxisDistanceToFarEdge(direction, source, rect2);
+    }
+
+    /**
+     * Fudge-factor opportunity: how to calculate distance given major and
+     * minor axis distances.
+     * <p/>
+     * Warning: this fudge factor is finely tuned, be sure to run all focus
+     * tests if you dare tweak it.
+     */
+    private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+        return 13 * majorAxisDistance * majorAxisDistance
+                + minorAxisDistance * minorAxisDistance;
+    }
+
+    /**
+     * Is destRect a candidate for the next focus given the direction? This
+     * checks whether the dest is at least partially to the direction of
+     * (e.g. left of) from source.
+     * <p/>
+     * Includes an edge case for an empty rect,which is used in some cases
+     * when searching from a point on the screen.
+     */
+    private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect,
+            @FocusRealDirection int direction) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
+                        && srcRect.left > destRect.left;
+            case View.FOCUS_RIGHT:
+                return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+                        && srcRect.right < destRect.right;
+            case View.FOCUS_UP:
+                return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+                        && srcRect.top > destRect.top;
+            case View.FOCUS_DOWN:
+                return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+                        && srcRect.bottom < destRect.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+
+    /**
+     * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
+     *
+     * @param direction the direction (up, down, left, right)
+     * @param rect1     the first rectangle
+     * @param rect2     the second rectangle
+     * @return whether the beams overlap
+     */
+    private static boolean beamsOverlap(@FocusRealDirection int direction,
+            @NonNull Rect rect1, @NonNull Rect rect2) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * e.g for left, is 'to left of'
+     */
+    private static boolean isToDirectionOf(@FocusRealDirection int direction,
+            @NonNull Rect src, @NonNull Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return src.left >= dest.right;
+            case View.FOCUS_RIGHT:
+                return src.right <= dest.left;
+            case View.FOCUS_UP:
+                return src.top >= dest.bottom;
+            case View.FOCUS_DOWN:
+                return src.bottom <= dest.top;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return the distance from the edge furthest in the given direction
+     * of source to the edge nearest in the given direction of
+     * dest. If the dest is not in the direction from source,
+     * returns 0.
+     */
+    private static int majorAxisDistance(@FocusRealDirection int direction,
+            @NonNull Rect source, @NonNull Rect dest) {
+        return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+    }
+
+    private static int majorAxisDistanceRaw(@FocusRealDirection int direction,
+            @NonNull Rect source, @NonNull Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.right;
+            case View.FOCUS_RIGHT:
+                return dest.left - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.bottom;
+            case View.FOCUS_DOWN:
+                return dest.top - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * @return the distance along the major axis w.r.t the direction from
+     * the edge of source to the far edge of dest. If the dest is
+     * not in the direction from source, returns 1 to break ties
+     * with {@link #majorAxisDistance}.
+     */
+    private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction,
+            @NonNull Rect source, @NonNull Rect dest) {
+        return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+    }
+
+    private static int majorAxisDistanceToFarEdgeRaw(
+            @FocusRealDirection int direction, @NonNull Rect source,
+            @NonNull Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+                return source.left - dest.left;
+            case View.FOCUS_RIGHT:
+                return dest.right - source.right;
+            case View.FOCUS_UP:
+                return source.top - dest.top;
+            case View.FOCUS_DOWN:
+                return dest.bottom - source.bottom;
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Finds the distance on the minor axis w.r.t the direction to the
+     * nearest edge of the destination rectangle.
+     *
+     * @param direction the direction (up, down, left, right)
+     * @param source the source rect
+     * @param dest the destination rect
+     * @return the distance
+     */
+    private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source,
+            @NonNull Rect dest) {
+        switch (direction) {
+            case View.FOCUS_LEFT:
+            case View.FOCUS_RIGHT:
+                // the distance between the center verticals
+                return Math.abs(
+                        ((source.top + source.height() / 2) -
+                                ((dest.top + dest.height() / 2))));
+            case View.FOCUS_UP:
+            case View.FOCUS_DOWN:
+                // the distance between the center horizontals
+                return Math.abs(
+                        ((source.left + source.width() / 2) -
+                                ((dest.left + dest.width() / 2))));
+        }
+        throw new IllegalArgumentException("direction must be one of "
+                + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+    }
+
+    /**
+     * Adapter used to obtain bounds from a generic data type.
+     */
+    public interface BoundsAdapter<T> {
+        void obtainBounds(T data, Rect outBounds);
+    }
+
+    /**
+     * Adapter used to obtain items from a generic collection type.
+     */
+    public interface CollectionAdapter<T, V> {
+        V get(T collection, int index);
+        int size(T collection);
+    }
+}
diff --git a/v4/java/android/support/v4/widget/PopupWindowCompat.java b/v4/java/android/support/v4/widget/PopupWindowCompat.java
index 17fc95b..68dbb02 100644
--- a/v4/java/android/support/v4/widget/PopupWindowCompat.java
+++ b/v4/java/android/support/v4/widget/PopupWindowCompat.java
@@ -16,6 +16,9 @@
 
 package android.support.v4.widget;
 
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.PopupWindow;
@@ -43,6 +46,13 @@
         @Override
         public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
                 int gravity) {
+            final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
+                    ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+                // Flip the location to align the right sides of the popup and
+                // anchor instead of left.
+                xoff -= (popup.getWidth() - anchor.getWidth());
+            }
             popup.showAsDropDown(anchor, xoff, yoff);
         }
 
diff --git a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
index ca71e9e..12a85bf 100644
--- a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
+++ b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
@@ -63,7 +63,7 @@
         super(context, layout, c);
         mTo = to;
         mOriginalFrom = from;
-        findColumns(from);
+        findColumns(c, from);
     }
 
     /**
@@ -89,7 +89,7 @@
         super(context, layout, c, flags);
         mTo = to;
         mOriginalFrom = from;
-        findColumns(from);
+        findColumns(c, from);
     }
 
     /**
@@ -109,10 +109,9 @@
      *
      * @throws IllegalStateException if binding cannot occur
      * 
-     * @see android.widget.CursorAdapter#bindView(android.view.View,
-     *      android.content.Context, android.database.Cursor)
+     * @see android.widget.CursorAdapter#bindView(View, Context, Cursor)
      * @see #getViewBinder()
-     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     * @see #setViewBinder(ViewBinder)
      * @see #setViewImage(ImageView, String)
      * @see #setViewText(TextView, String)
      */
@@ -156,7 +155,7 @@
      * @return a ViewBinder or null if the binder does not exist
      *
      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
-     * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+     * @see #setViewBinder(ViewBinder)
      */
     public ViewBinder getViewBinder() {
         return mViewBinder;
@@ -201,7 +200,7 @@
     /**
      * Called by bindView() to set the text for a TextView but only if
      * there is no existing ViewBinder or if the existing ViewBinder cannot
-     * handle binding to an TextView.
+     * handle binding to a TextView.
      *
      * Intended to be overridden by Adapters that need to filter strings
      * retrieved from the database.
@@ -221,7 +220,7 @@
      *
      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
      * @see #setStringConversionColumn(int) 
-     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #setCursorToStringConverter(CursorToStringConverter)
      * @see #getCursorToStringConverter()
      */
     public int getStringConversionColumn() {
@@ -239,7 +238,7 @@
      *
      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
      * @see #getStringConversionColumn()
-     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #setCursorToStringConverter(CursorToStringConverter)
      * @see #getCursorToStringConverter()
      */
     public void setStringConversionColumn(int stringConversionColumn) {
@@ -253,7 +252,7 @@
      * @return null if the converter does not exist or an instance of
      *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
      *
-     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+     * @see #setCursorToStringConverter(CursorToStringConverter)
      * @see #getStringConversionColumn()
      * @see #setStringConversionColumn(int)
      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -269,7 +268,7 @@
      * @param cursorToStringConverter the Cursor to String converter, or
      *        null to remove the converter
      *
-     * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) 
+     * @see #setCursorToStringConverter(CursorToStringConverter)
      * @see #getStringConversionColumn()
      * @see #setStringConversionColumn(int)
      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -301,20 +300,21 @@
     }
 
     /**
-     * Create a map from an array of strings to an array of column-id integers in mCursor.
-     * If mCursor is null, the array will be discarded.
-     * 
+     * Create a map from an array of strings to an array of column-id integers in cursor c.
+     * If c is null, the array will be discarded.
+     *
+     * @param c the cursor to find the columns from
      * @param from the Strings naming the columns of interest
      */
-    private void findColumns(String[] from) {
-        if (mCursor != null) {
+    private void findColumns(Cursor c, String[] from) {
+        if (c != null) {
             int i;
             int count = from.length;
             if (mFrom == null || mFrom.length != count) {
                 mFrom = new int[count];
             }
             for (i = 0; i < count; i++) {
-                mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
+                mFrom[i] = c.getColumnIndexOrThrow(from[i]);
             }
         } else {
             mFrom = null;
@@ -323,15 +323,16 @@
 
     @Override
     public Cursor swapCursor(Cursor c) {
-        Cursor res = super.swapCursor(c);
-        // rescan columns in case cursor layout is different
-        findColumns(mOriginalFrom);
-        return res;
+        // super.swapCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        return super.swapCursor(c);
     }
-    
+
     /**
      * Change the cursor and change the column-to-view mappings at the same time.
-     *  
+     *
      * @param c The database cursor.  Can be null if the cursor is not available yet.
      * @param from A list of column names representing the data to bind to the UI.  Can be null 
      *            if the cursor is not available yet.
@@ -343,8 +344,11 @@
     public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
         mOriginalFrom = from;
         mTo = to;
-        super.changeCursor(c);        
-        findColumns(mOriginalFrom);
+        // super.changeCursor() will notify observers before we have
+        // a valid mapping, make sure we have a mapping before this
+        // happens
+        findColumns(c, mOriginalFrom);
+        super.changeCursor(c);
     }
 
     /**
@@ -356,11 +360,11 @@
      * change the way binding occurs for views supported by
      * SimpleCursorAdapter.
      *
-     * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+     * @see SimpleCursorAdapter#bindView(View, Context, Cursor)
      * @see SimpleCursorAdapter#setViewImage(ImageView, String) 
      * @see SimpleCursorAdapter#setViewText(TextView, String)
      */
-    public static interface ViewBinder {
+    public interface ViewBinder {
         /**
          * Binds the Cursor column defined by the specified index to the specified view.
          *
@@ -383,7 +387,7 @@
      *
      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
      */
-    public static interface CursorToStringConverter {
+    public interface CursorToStringConverter {
         /**
          * Returns a CharSequence representing the specified Cursor.
          *
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index cbd4b87..2059557 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -21,6 +21,7 @@
 import android.content.res.TypedArray;
 import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.NestedScrollingChild;
 import android.support.v4.view.NestedScrollingChildHelper;
@@ -68,14 +69,16 @@
     // Maps to ProgressBar default style
     public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
 
+    @VisibleForTesting
+    static final int CIRCLE_DIAMETER = 40;
+    @VisibleForTesting
+    static final int CIRCLE_DIAMETER_LARGE = 56;
+
     private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
 
     private static final int MAX_ALPHA = 255;
     private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
 
-    private static final int CIRCLE_DIAMETER = 40;
-    private static final int CIRCLE_DIAMETER_LARGE = 56;
-
     private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
     private static final int INVALID_POINTER = -1;
     private static final float DRAG_RATE = .5f;
@@ -538,8 +541,7 @@
      *
      * @param colors
      */
-    @ColorInt
-    public void setColorSchemeColors(int... colors) {
+    public void setColorSchemeColors(@ColorInt int... colors) {
         ensureTarget();
         mProgress.setColorSchemeColors(colors);
     }
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java
similarity index 60%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java
index 8126b38..b16db18 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
+package android.support.v4.content.res;
 
-import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
 
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+class ConfigurationHelperJellybeanMr1 {
+    static int getDensityDpi(@NonNull Resources resources) {
+        return resources.getConfiguration().densityDpi;
+    }
 }
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 091d5a4..93eb3f4 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -37,6 +37,7 @@
 import android.print.PrintDocumentAdapter;
 import android.print.PrintDocumentInfo;
 import android.print.PrintManager;
+import android.print.PrintAttributes.MediaSize;
 import android.print.pdf.PrintedPdfDocument;
 import android.util.Log;
 
@@ -87,13 +88,27 @@
         public void onFinish();
     }
 
+    /**
+     * Whether the PrintActivity respects the suggested orientation
+     */
+    protected boolean mPrintActivityRespectsOrientation;
+
+    /**
+     * Whether the print subsystem handles min margins correctly. If not the print helper needs to
+     * fake this.
+     */
+    protected boolean mIsMinMarginsHandlingCorrect;
+
     int mScaleMode = SCALE_MODE_FILL;
 
     int mColorMode = COLOR_MODE_COLOR;
 
-    int mOrientation = ORIENTATION_LANDSCAPE;
+    int mOrientation;
 
     PrintHelperKitkat(Context context) {
+        mPrintActivityRespectsOrientation = true;
+        mIsMinMarginsHandlingCorrect = true;
+
         mContext = context;
     }
 
@@ -150,6 +165,10 @@
      * {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}
      */
     public int getOrientation() {
+        /// Unset defaults to landscape but might turn image
+        if (mOrientation == 0) {
+            return ORIENTATION_LANDSCAPE;
+        }
         return mOrientation;
     }
 
@@ -164,6 +183,35 @@
     }
 
     /**
+     * Check if the supplied bitmap should best be printed on a portrait orientation paper.
+     *
+     * @param bitmap The bitmap to be printed.
+     * @return true iff the picture should best be printed on a portrait orientation paper.
+     */
+    private static boolean isPortrait(Bitmap bitmap) {
+        if (bitmap.getWidth() <= bitmap.getHeight()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Create a build with a copy from the other print attributes.
+     *
+     * @param other The other print attributes
+     *
+     * @return A builder that will build print attributes that match the other attributes
+     */
+    protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
+        return (new PrintAttributes.Builder())
+                .setMediaSize(other.getMediaSize())
+                .setResolution(other.getResolution())
+                .setColorMode(other.getColorMode())
+                .setMinMargins(other.getMinMargins());
+    }
+
+    /**
      * Prints a bitmap.
      *
      * @param jobName The print job name.
@@ -177,8 +225,10 @@
         }
         final int fittingMode = mScaleMode; // grab the fitting mode at time of call
         PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
-        PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
-        if (bitmap.getWidth() > bitmap.getHeight()) {
+        PrintAttributes.MediaSize mediaSize;
+        if (isPortrait(bitmap)) {
+            mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
+        } else {
             mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE;
         }
         PrintAttributes attr = new PrintAttributes.Builder()
@@ -211,54 +261,8 @@
                     public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
                                         CancellationSignal cancellationSignal,
                                         WriteResultCallback writeResultCallback) {
-                        PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
-                                mAttributes);
-
-                        Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap,
-                                mAttributes.getColorMode());
-                        try {
-                            Page page = pdfDocument.startPage(1);
-
-                            RectF content = new RectF(page.getInfo().getContentRect());
-
-                            Matrix matrix = getMatrix(
-                                    maybeGrayscale.getWidth(), maybeGrayscale.getHeight(),
-                                    content, fittingMode);
-
-                            // Draw the bitmap.
-                            page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
-
-                            // Finish the page.
-                            pdfDocument.finishPage(page);
-
-                            try {
-                                // Write the document.
-                                pdfDocument.writeTo(new FileOutputStream(
-                                        fileDescriptor.getFileDescriptor()));
-                                // Done.
-                                writeResultCallback.onWriteFinished(
-                                        new PageRange[]{PageRange.ALL_PAGES});
-                            } catch (IOException ioe) {
-                                // Failed.
-                                Log.e(LOG_TAG, "Error writing printed content", ioe);
-                                writeResultCallback.onWriteFailed(null);
-                            }
-                        } finally {
-                            if (pdfDocument != null) {
-                                pdfDocument.close();
-                            }
-                            if (fileDescriptor != null) {
-                                try {
-                                    fileDescriptor.close();
-                                } catch (IOException ioe) {
-                                    /* ignore */
-                                }
-                            }
-                            // If we created a new instance for grayscaling, then recycle it here.
-                            if (maybeGrayscale != bitmap) {
-                                maybeGrayscale.recycle();
-                            }
-                        }
+                        writeBitmap(mAttributes, fittingMode, bitmap, fileDescriptor,
+                                writeResultCallback);
                     }
 
                     @Override
@@ -301,6 +305,98 @@
     }
 
     /**
+     * Write a bitmap for a PDF document.
+     *
+     * @param attributes          The print attributes
+     * @param fittingMode         How to fit the bitmap
+     * @param bitmap              The bitmap to write
+     * @param fileDescriptor      The file to write to
+     * @param writeResultCallback Callback to call once written
+     */
+    private void writeBitmap(PrintAttributes attributes, int fittingMode, Bitmap bitmap,
+            ParcelFileDescriptor fileDescriptor,
+            PrintDocumentAdapter.WriteResultCallback writeResultCallback) {
+        PrintAttributes pdfAttributes;
+        if (mIsMinMarginsHandlingCorrect) {
+            pdfAttributes = attributes;
+        } else {
+            // If the handling of any margin != 0 is broken, strip the margins and add them to the
+            // bitmap later
+            pdfAttributes = copyAttributes(attributes)
+                    .setMinMargins(new PrintAttributes.Margins(0,0,0,0)).build();
+        }
+
+        PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
+                pdfAttributes);
+
+        Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap,
+                pdfAttributes.getColorMode());
+        try {
+            Page page = pdfDocument.startPage(1);
+
+            RectF contentRect;
+            if (mIsMinMarginsHandlingCorrect) {
+                contentRect = new RectF(page.getInfo().getContentRect());
+            } else {
+                // Create dummy doc that has the margins to compute correctly sized content
+                // rectangle
+                PrintedPdfDocument dummyDocument = new PrintedPdfDocument(mContext,
+                        attributes);
+                Page dummyPage = dummyDocument.startPage(1);
+                contentRect = new RectF(dummyPage.getInfo().getContentRect());
+                dummyDocument.finishPage(dummyPage);
+                dummyDocument.close();
+            }
+
+            // Resize bitmap
+            Matrix matrix = getMatrix(
+                    maybeGrayscale.getWidth(), maybeGrayscale.getHeight(),
+                    contentRect, fittingMode);
+
+            if (mIsMinMarginsHandlingCorrect) {
+                // The pdfDocument takes care of the positioning and margins
+            } else {
+                // Move it to the correct position.
+                matrix.postTranslate(contentRect.left, contentRect.top);
+
+                // Cut off margins
+                page.getCanvas().clipRect(contentRect);
+            }
+
+            // Draw the bitmap.
+            page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
+
+            // Finish the page.
+            pdfDocument.finishPage(page);
+
+            try {
+                // Write the document.
+                pdfDocument.writeTo(new FileOutputStream(fileDescriptor.getFileDescriptor()));
+                // Done.
+                writeResultCallback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+            } catch (IOException ioe) {
+                // Failed.
+                Log.e(LOG_TAG, "Error writing printed content", ioe);
+                writeResultCallback.onWriteFailed(null);
+            }
+        } finally {
+            pdfDocument.close();
+
+            if (fileDescriptor != null) {
+                try {
+                    fileDescriptor.close();
+                } catch (IOException ioe) {
+                    // ignore
+                }
+            }
+            // If we created a new instance for grayscaling, then recycle it here.
+            if (maybeGrayscale != bitmap) {
+                maybeGrayscale.recycle();
+            }
+        }
+    }
+
+    /**
      * Prints an image located at the Uri. Image types supported are those of
      * <code>BitmapFactory.decodeStream</code> (JPEG, GIF, PNG, BMP, WEBP)
      *
@@ -325,7 +421,9 @@
                                  final LayoutResultCallback layoutResultCallback,
                                  Bundle bundle) {
 
-                mAttributes = newPrintAttributes;
+                synchronized (this) {
+                    mAttributes = newPrintAttributes;
+                }
 
                 if (cancellationSignal.isCanceled()) {
                     layoutResultCallback.onLayoutCancelled();
@@ -343,7 +441,6 @@
                 }
 
                 mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
-
                     @Override
                     protected void onPreExecute() {
                         // First register for cancellation requests.
@@ -370,12 +467,35 @@
                     @Override
                     protected void onPostExecute(Bitmap bitmap) {
                         super.onPostExecute(bitmap);
+
+                        // If orientation was not set by the caller, try to fit the bitmap on
+                        // the current paper by potentially rotating the bitmap by 90 degrees.
+                        if (bitmap != null
+                                && (!mPrintActivityRespectsOrientation || mOrientation == 0)) {
+                            MediaSize mediaSize;
+
+                            synchronized (this) {
+                                mediaSize = mAttributes.getMediaSize();
+                            }
+
+                            if (mediaSize != null) {
+                                if (mediaSize.isPortrait() != isPortrait(bitmap)) {
+                                    Matrix rotation = new Matrix();
+
+                                    rotation.postRotate(90);
+                                    bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+                                            bitmap.getHeight(), rotation, true);
+                                }
+                            }
+                        }
+
                         mBitmap = bitmap;
                         if (bitmap != null) {
                             PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName)
                                     .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
                                     .setPageCount(1)
                                     .build();
+
                             boolean changed = !newPrintAttributes.equals(oldPrintAttributes);
 
                             layoutResultCallback.onLayoutFinished(info, changed);
@@ -424,53 +544,7 @@
             public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
                                 CancellationSignal cancellationSignal,
                                 WriteResultCallback writeResultCallback) {
-                PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
-                        mAttributes);
-                Bitmap maybeGrayscale = convertBitmapForColorMode(mBitmap,
-                        mAttributes.getColorMode());
-                try {
-
-                    Page page = pdfDocument.startPage(1);
-                    RectF content = new RectF(page.getInfo().getContentRect());
-
-                    // Compute and apply scale to fill the page.
-                    Matrix matrix = getMatrix(mBitmap.getWidth(), mBitmap.getHeight(),
-                            content, fittingMode);
-
-                    // Draw the bitmap.
-                    page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
-
-                    // Finish the page.
-                    pdfDocument.finishPage(page);
-
-                    try {
-                        // Write the document.
-                        pdfDocument.writeTo(new FileOutputStream(
-                                fileDescriptor.getFileDescriptor()));
-                        // Done.
-                        writeResultCallback.onWriteFinished(
-                                new PageRange[]{PageRange.ALL_PAGES});
-                    } catch (IOException ioe) {
-                        // Failed.
-                        Log.e(LOG_TAG, "Error writing printed content", ioe);
-                        writeResultCallback.onWriteFailed(null);
-                    }
-                } finally {
-                    if (pdfDocument != null) {
-                        pdfDocument.close();
-                    }
-                    if (fileDescriptor != null) {
-                        try {
-                            fileDescriptor.close();
-                        } catch (IOException ioe) {
-                            /* ignore */
-                        }
-                    }
-                    // If we created a new instance for grayscaling, then recycle it here.
-                    if (maybeGrayscale != mBitmap) {
-                        maybeGrayscale.recycle();
-                    }
-                }
+                writeBitmap(mAttributes, fittingMode, mBitmap, fileDescriptor, writeResultCallback);
             }
         };
 
@@ -478,7 +552,7 @@
         PrintAttributes.Builder builder = new PrintAttributes.Builder();
         builder.setColorMode(mColorMode);
 
-        if (mOrientation == ORIENTATION_LANDSCAPE) {
+        if (mOrientation == ORIENTATION_LANDSCAPE || mOrientation == 0) {
             builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE);
         } else if (mOrientation == ORIENTATION_PORTRAIT) {
             builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT);
@@ -583,4 +657,4 @@
 
         return grayscale;
     }
-}
\ No newline at end of file
+}
diff --git a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
index 3805d02..0629d24 100644
--- a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
@@ -23,6 +23,9 @@
  * KitKat-specific AccessibilityNodeInfo API implementation.
  */
 class AccessibilityNodeInfoCompatKitKat {
+    private static final String ROLE_DESCRIPTION_KEY =
+            "AccessibilityNodeInfo.roleDescription";
+
     static int getLiveRegion(Object info) {
         return ((AccessibilityNodeInfo) info).getLiveRegion();
     }
@@ -112,6 +115,16 @@
         ((AccessibilityNodeInfo) info).setMultiLine(multiLine);
     }
 
+    public static CharSequence getRoleDescription(Object info) {
+        Bundle extras = getExtras(info);
+        return extras.getCharSequence(ROLE_DESCRIPTION_KEY);
+    }
+
+    public static void setRoleDescription(Object info, CharSequence roleDescription) {
+        Bundle extras = getExtras(info);
+        extras.putCharSequence(ROLE_DESCRIPTION_KEY, roleDescription);
+    }
+
     static class CollectionInfo {
         static int getColumnCount(Object info) {
             return ((AccessibilityNodeInfo.CollectionInfo) info).getColumnCount();
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index f0fea8f..89b2335 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -32,18 +32,20 @@
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.support.v4.widget.TextViewTestActivity"/>
 
+        <activity android:name="android.support.v4.widget.SwipeRefreshLayoutActivity"/>
+
         <activity android:name="android.support.v4.view.ViewPagerWithTitleStripActivity"/>
 
         <activity android:name="android.support.v4.view.ViewPagerWithTabStripActivity"/>
 
-        <activity android:name="android.support.v4.widget.TestActivity"/>
-
         <activity android:name="android.support.v4.view.VpaActivity"/>
 
         <activity
             android:name="android.support.v4.ThemedYellowActivity"
             android:theme="@style/YellowTheme" />
         <activity android:name="android.support.v4.app.test.FragmentTestActivity"/>
+
+        <activity android:name="android.support.v4.app.test.EmptyFragmentTestActivity" />
     </application>
 
     <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
new file mode 100644
index 0000000..eb11183
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.app;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.EmptyFragmentTestActivity;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.*;
+
+@RunWith(AndroidJUnit4.class)
+public class FragmentLifecycleTest {
+
+    @Rule
+    public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
+            new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
+
+    @Test
+    @MediumTest
+    public void basicLifecycle() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictFragment strictFragment = new StrictFragment();
+
+        // Add fragment; StrictFragment will throw if it detects any violation
+        // in standard lifecycle method ordering or expected preconditions.
+        fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment is not added", strictFragment.isAdded());
+        assertFalse("fragment is detached", strictFragment.isDetached());
+        assertTrue("fragment is not resumed", strictFragment.isResumed());
+
+        // Test removal as well; StrictFragment will throw here too.
+        fm.beginTransaction().remove(strictFragment).commit();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment is added", strictFragment.isAdded());
+        assertFalse("fragment is resumed", strictFragment.isResumed());
+
+        // This one is perhaps counterintuitive; "detached" means specifically detached
+        // but still managed by a FragmentManager. The .remove call above
+        // should not enter this state.
+        assertFalse("fragment is detached", strictFragment.isDetached());
+    }
+
+    @Test
+    @MediumTest
+    public void detachment() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictFragment f1 = new StrictFragment();
+        final StrictFragment f2 = new StrictFragment();
+
+        fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+        assertTrue("fragment 2 is not added", f2.isAdded());
+
+        // Test detaching fragments using StrictFragment to throw on errors.
+        fm.beginTransaction().detach(f1).detach(f2).commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not detached", f1.isDetached());
+        assertTrue("fragment 2 is not detached", f2.isDetached());
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertFalse("fragment 2 is added", f2.isAdded());
+
+        // Only reattach f1; leave v2 detached.
+        fm.beginTransaction().attach(f1).commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+        assertFalse("fragment 1 is detached", f1.isDetached());
+        assertTrue("fragment 2 is not detached", f2.isDetached());
+
+        // Remove both from the FragmentManager.
+        fm.beginTransaction().remove(f1).remove(f2).commit();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertFalse("fragment 2 is added", f2.isAdded());
+        assertFalse("fragment 1 is detached", f1.isDetached());
+        assertFalse("fragment 2 is detached", f2.isDetached());
+    }
+
+    @Test
+    @MediumTest
+    public void basicBackStack() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictFragment f1 = new StrictFragment();
+        final StrictFragment f2 = new StrictFragment();
+
+        // Add a fragment normally to set up
+        fm.beginTransaction().add(f1, "1").commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+
+        // Remove the first one and add a second. We're not using replace() here since
+        // these fragments are headless and as of this test writing, replace() only works
+        // for fragments with views and a container view id.
+        // Add it to the back stack so we can pop it afterwards.
+        fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertTrue("fragment 2 is not added", f2.isAdded());
+
+        // Test popping the stack
+        fm.popBackStack();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment 2 is added", f2.isAdded());
+        assertTrue("fragment 1 is not added", f1.isAdded());
+    }
+
+    @Test
+    @MediumTest
+    public void attachBackStack() throws Throwable {
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictFragment f1 = new StrictFragment();
+        final StrictFragment f2 = new StrictFragment();
+
+        // Add a fragment normally to set up
+        fm.beginTransaction().add(f1, "1").commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+
+        fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not detached", f1.isDetached());
+        assertFalse("fragment 2 is detached", f2.isDetached());
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertTrue("fragment 2 is not added", f2.isAdded());
+    }
+
+    @Test
+    @MediumTest
+    public void viewLifecycle() throws Throwable {
+        // Test basic lifecycle when the fragment creates a view
+
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictViewFragment f1 = new StrictViewFragment();
+
+        fm.beginTransaction().add(android.R.id.content, f1).commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+        final View view = f1.getView();
+        assertNotNull("fragment 1 returned null from getView", view);
+        assertTrue("fragment 1's view is not attached to a window", view.isAttachedToWindow());
+
+        fm.beginTransaction().remove(f1).commit();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
+        assertFalse("fragment 1's previous view is still attached to a window",
+                view.isAttachedToWindow());
+    }
+
+    @Test
+    @MediumTest
+    public void viewReplace() throws Throwable {
+        // Replace one view with another, then reverse it with the back stack
+
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+        final StrictViewFragment f1 = new StrictViewFragment();
+        final StrictViewFragment f2 = new StrictViewFragment();
+
+        fm.beginTransaction().add(android.R.id.content, f1).commit();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+
+        View origView1 = f1.getView();
+        assertNotNull("fragment 1 returned null view", origView1);
+        assertTrue("fragment 1's view not attached", origView1.isAttachedToWindow());
+
+        fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
+        executePendingTransactions(fm);
+
+        assertFalse("fragment 1 is added", f1.isAdded());
+        assertTrue("fragment 2 is added", f2.isAdded());
+        assertNull("fragment 1 returned non-null view", f1.getView());
+        assertFalse("fragment 1's old view still attached", origView1.isAttachedToWindow());
+        View origView2 = f2.getView();
+        assertNotNull("fragment 2 returned null view", origView2);
+        assertTrue("fragment 2's view not attached", origView2.isAttachedToWindow());
+
+        fm.popBackStack();
+        executePendingTransactions(fm);
+
+        assertTrue("fragment 1 is not added", f1.isAdded());
+        assertFalse("fragment 2 is added", f2.isAdded());
+        assertNull("fragment 2 returned non-null view", f2.getView());
+        assertFalse("fragment 2's view still attached", origView2.isAttachedToWindow());
+        View newView1 = f1.getView();
+        assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
+        assertTrue("fragment 1's view not attached", newView1.isAttachedToWindow());
+    }
+
+    private void executePendingTransactions(final FragmentManager fm) throws Throwable {
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                fm.executePendingTransactions();
+            }
+        });
+    }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentTest.java b/v4/tests/java/android/support/v4/app/FragmentTest.java
new file mode 100644
index 0000000..50ec5a7
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.app;
+
+import android.os.Bundle;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.test.R;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Miscellaneous tests for fragments that aren't big enough to belong to their own classes.
+ */
+public class FragmentTest extends
+        ActivityInstrumentationTestCase2<FragmentTestActivity> {
+    private FragmentTestActivity mActivity;
+
+    public FragmentTest() {
+        super(FragmentTestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+    }
+
+    @SmallTest
+    @UiThreadTest
+    public void testOnCreateOrder() throws Throwable {
+        OrderFragment fragment1 = new OrderFragment();
+        OrderFragment fragment2 = new OrderFragment();
+        mActivity.getSupportFragmentManager()
+                .beginTransaction()
+                .add(R.id.content, fragment1)
+                .add(R.id.content, fragment2)
+                .commitNow();
+        assertEquals(0, fragment1.createOrder);
+        assertEquals(1, fragment2.createOrder);
+    }
+
+    @SmallTest
+    public void testChildFragmentManagerGone() throws Throwable {
+        final FragmentA fragmentA = new FragmentA();
+        final FragmentB fragmentB = new FragmentB();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getSupportFragmentManager().beginTransaction()
+                        .add(R.id.content, fragmentA)
+                        .commitNow();
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getSupportFragmentManager().beginTransaction()
+                        .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
+                                R.anim.long_fade_in, R.anim.long_fade_out)
+                        .replace(R.id.content, fragmentB)
+                        .addToBackStack(null)
+                        .commit();
+            }
+        });
+        // Wait for the middle of the animation
+        Thread.sleep(150);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getSupportFragmentManager().beginTransaction()
+                        .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
+                                R.anim.long_fade_in, R.anim.long_fade_out)
+                        .replace(R.id.content, fragmentA)
+                        .addToBackStack(null)
+                        .commit();
+            }
+        });
+        // Wait for the middle of the animation
+        Thread.sleep(150);
+        getInstrumentation().waitForIdleSync();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getSupportFragmentManager().popBackStack();
+            }
+        });
+        // Wait for the middle of the animation
+        Thread.sleep(150);
+        getInstrumentation().waitForIdleSync();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getSupportFragmentManager().popBackStack();
+            }
+        });
+    }
+
+    @MediumTest
+    @UiThreadTest
+    public void testViewOrder() throws Throwable {
+        FragmentA fragmentA = new FragmentA();
+        FragmentB fragmentB = new FragmentB();
+        FragmentC fragmentC = new FragmentC();
+        mActivity.getSupportFragmentManager()
+                .beginTransaction()
+                .add(R.id.content, fragmentA)
+                .add(R.id.content, fragmentB)
+                .add(R.id.content, fragmentC)
+                .commitNow();
+        ViewGroup content = (ViewGroup) mActivity.findViewById(R.id.content);
+        assertEquals(3, content.getChildCount());
+        assertNotNull(content.getChildAt(0).findViewById(R.id.textA));
+        assertNotNull(content.getChildAt(1).findViewById(R.id.textB));
+        assertNotNull(content.getChildAt(2).findViewById(R.id.textC));
+    }
+
+    public static class OrderFragment extends Fragment {
+        private static AtomicInteger sOrder = new AtomicInteger();
+        public int createOrder = -1;
+
+        public OrderFragment() {}
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            createOrder = sOrder.getAndIncrement();
+            super.onCreate(savedInstanceState);
+        }
+    }
+
+    public static class FragmentA extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.fragment_a, container, false);
+        }
+    }
+
+    public static class FragmentB extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.fragment_b, container, false);
+        }
+    }
+
+    public static class FragmentC extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            return inflater.inflate(R.layout.fragment_c, container, false);
+        }
+    }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
index 27f277b..f49d8e2 100644
--- a/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
+++ b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
@@ -15,9 +15,9 @@
  */
 package android.support.v4.app;
 
-import android.os.SystemClock;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
 import android.support.v4.app.test.FragmentTestActivity;
-import android.support.v4.app.test.FragmentTestActivity.OnTransitionListener;
 import android.support.v4.app.test.FragmentTestActivity.TestFragment;
 import android.support.v4.test.R;
 import android.support.v4.view.ViewCompat;
@@ -47,6 +47,9 @@
     }
 
     public void testFragmentTransition() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         launchStartFragment();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -82,6 +85,9 @@
     }
 
     public void testFirstOutLastInTransition() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         launchStartFragment();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -136,6 +142,9 @@
     }
 
     public void testPopTwo() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         launchStartFragment();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -204,6 +213,9 @@
     }
 
     public void testNullTransition() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         getInstrumentation().waitForIdleSync();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -271,6 +283,9 @@
     }
 
     public void testRemoveAdded() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         launchStartFragment();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -285,9 +300,7 @@
                 mActivity.getSupportFragmentManager().executePendingTransactions();
             }
         });
-        waitForEnd(mEndFragment, TestFragment.ENTER);
-        assertTrue(mEndFragment.wasEndCalled(TestFragment.ENTER));
-        assertTrue(mStartFragment.wasEndCalled(TestFragment.EXIT));
+        assertTrue(waitForEnd(mEndFragment, TestFragment.ENTER));
         runTestOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -295,12 +308,13 @@
                 mActivity.getSupportFragmentManager().executePendingTransactions();
             }
         });
-        waitForEnd(mStartFragment, TestFragment.REENTER);
-        assertTrue(mStartFragment.wasEndCalled(TestFragment.REENTER));
-        assertTrue(mEndFragment.wasEndCalled(TestFragment.RETURN));
+        assertTrue(waitForEnd(mStartFragment, TestFragment.REENTER));
     }
 
     public void testAddRemoved() throws Throwable {
+        if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+            return;
+        }
         launchStartFragment();
         runTestOnUiThread(new Runnable() {
             @Override
@@ -345,25 +359,12 @@
                 mActivity.getSupportFragmentManager().executePendingTransactions();
             }
         });
-        waitForEnd(mStartFragment, TestFragment.ENTER);
-        assertTrue(mStartFragment.wasEndCalled(TestFragment.ENTER));
+        assertTrue(waitForEnd(mStartFragment, TestFragment.ENTER));
         mStartFragment.clearNotifications();
     }
 
     private boolean waitForStart(TestFragment fragment, int key) throws InterruptedException {
-        final boolean started;
-        WaitForTransition listener = new WaitForTransition(key, true);
-        fragment.setOnTransitionListener(listener);
-        final long endTime = SystemClock.uptimeMillis() + 100;
-        synchronized (listener) {
-            long waitTime;
-            while ((waitTime = endTime - SystemClock.uptimeMillis()) > 0 &&
-                    !listener.isDone()) {
-                listener.wait(waitTime);
-            }
-            started = listener.isDone();
-        }
-        fragment.setOnTransitionListener(null);
+        boolean started = fragment.waitForStart(key);
         getInstrumentation().waitForIdleSync();
         return started;
     }
@@ -372,52 +373,8 @@
         if (!waitForStart(fragment, key)) {
             return false;
         }
-        final boolean ended;
-        WaitForTransition listener = new WaitForTransition(key, false);
-        fragment.setOnTransitionListener(listener);
-        final long endTime = SystemClock.uptimeMillis() + 400;
-        synchronized (listener) {
-            long waitTime;
-            while ((waitTime = endTime - SystemClock.uptimeMillis()) > 0 &&
-                    !listener.isDone()) {
-                listener.wait(waitTime);
-            }
-            ended = listener.isDone();
-        }
-        fragment.setOnTransitionListener(null);
+        final boolean ended = fragment.waitForEnd(key);
         getInstrumentation().waitForIdleSync();
         return ended;
     }
-
-    private static class WaitForTransition implements OnTransitionListener {
-        final int key;
-        final boolean isStart;
-        boolean isDone;
-
-        public WaitForTransition(int key, boolean isStart) {
-            this.key = key;
-            this.isStart = isStart;
-        }
-
-        protected boolean isComplete(TestFragment fragment) {
-            if (isStart) {
-                return fragment.wasStartCalled(key);
-            } else {
-                return fragment.wasEndCalled(key);
-            }
-        }
-
-        public synchronized boolean isDone() {
-            return isDone;
-        }
-
-        @Override
-        public synchronized void onTransition(TestFragment fragment) {
-            isDone = isComplete(fragment);
-            if (isDone) {
-                notifyAll();
-            }
-        }
-    }
-
 }
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
new file mode 100644
index 0000000..2ec1a6c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment.OnAttachListener;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class NestedFragmentRestoreTest {
+
+    @Rule
+    public ActivityTestRule<FragmentTestActivity> mActivityRule = new ActivityTestRule<>(
+            FragmentTestActivity.class);
+
+    public NestedFragmentRestoreTest() {
+    }
+
+    @Test
+    @SmallTest
+    public void recreateActivity() throws Throwable {
+        final FragmentTestActivity activity = mActivityRule.getActivity();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                FragmentTestActivity.ParentFragment parent = new ParentFragment();
+                parent.setRetainChildInstance(true);
+
+                activity.getSupportFragmentManager().beginTransaction()
+                        .add(parent, "parent")
+                        .commitNow();
+            }
+        });
+
+        FragmentManager fm = activity.getSupportFragmentManager();
+        ParentFragment parent = (ParentFragment) fm.findFragmentByTag("parent");
+        ChildFragment child = parent.getChildFragment();
+
+        final Context[] attachedTo = new Context[1];
+        final CountDownLatch latch = new CountDownLatch(1);
+        child.setOnAttachListener(new OnAttachListener() {
+            @Override
+            public void onAttach(Context activity, ChildFragment fragment) {
+                attachedTo[0] = activity;
+                latch.countDown();
+            }
+        });
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+
+        assertTrue("timeout waiting for recreate", latch.await(10, TimeUnit.SECONDS));
+
+        assertNotNull("attached as part of recreate", attachedTo[0]);
+        assertNotSame("attached to new context", activity, attachedTo[0]);
+        assertNotSame("attached to new parent fragment", parent, child);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
index fdc60b7..cc884f9 100644
--- a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
 import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
@@ -57,7 +58,7 @@
     @UiThreadTest
     public void testThrowsWhenUsingReservedRequestCode() {
         try {
-            mParentFragment.childFragment.startActivityForResult(
+            mParentFragment.getChildFragment().startActivityForResult(
                 new Intent(Intent.ACTION_CALL), 16777216 /* requestCode */);
             fail("Expected IllegalArgumentException");
         } catch (IllegalArgumentException expected) {}
@@ -72,12 +73,12 @@
                         new IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */);
 
         // Sanity check that onActivityResult hasn't been called yet.
-        assertFalse(mParentFragment.childFragment.onActivityResultCalled);
+        assertFalse(mParentFragment.getChildFragment().onActivityResultCalled);
 
         final CountDownLatch latch = new CountDownLatch(1);
         getActivity().runOnUiThread(new Runnable() {
             public void run() {
-                mParentFragment.childFragment.startActivityForResult(
+                mParentFragment.getChildFragment().startActivityForResult(
                         new Intent(Intent.ACTION_CALL),
                         5 /* requestCode */);
                 latch.countDown();
@@ -87,9 +88,9 @@
 
         assertTrue(getInstrumentation().checkMonitorHit(activityMonitor, 1));
 
-        assertTrue(mParentFragment.childFragment.onActivityResultCalled);
-        assertEquals(5, mParentFragment.childFragment.onActivityResultRequestCode);
-        assertEquals(Activity.RESULT_OK,
-                mParentFragment.childFragment.onActivityResultResultCode);
+        final ChildFragment childFragment = mParentFragment.getChildFragment();
+        assertTrue(childFragment.onActivityResultCalled);
+        assertEquals(5, childFragment.onActivityResultRequestCode);
+        assertEquals(Activity.RESULT_OK, childFragment.onActivityResultResultCode);
     }
 }
diff --git a/v4/tests/java/android/support/v4/app/StrictFragment.java b/v4/tests/java/android/support/v4/app/StrictFragment.java
new file mode 100644
index 0000000..81798c4
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/StrictFragment.java
@@ -0,0 +1,174 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * This fragment watches its primary lifecycle events and throws IllegalStateException
+ * if any of them are called out of order or from a bad/unexpected state.
+ */
+public class StrictFragment extends Fragment {
+    public static final int DETACHED = 0;
+    public static final int ATTACHED = 1;
+    public static final int CREATED = 2;
+    public static final int ACTIVITY_CREATED = 3;
+    public static final int STARTED = 4;
+    public static final int RESUMED = 5;
+
+    int mState;
+
+    boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated,
+            mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState,
+            mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach;
+
+    static String stateToString(int state) {
+        switch (state) {
+            case DETACHED: return "DETACHED";
+            case ATTACHED: return "ATTACHED";
+            case CREATED: return "CREATED";
+            case ACTIVITY_CREATED: return "ACTIVITY_CREATED";
+            case STARTED: return "STARTED";
+            case RESUMED: return "RESUMED";
+        }
+        return "(unknown " + state + ")";
+    }
+
+    public void checkGetActivity() {
+        if (getActivity() == null) {
+            throw new IllegalStateException("getActivity() returned null at unexpected time");
+        }
+    }
+
+    public void checkState(String caller, int... expected) {
+        if (expected == null || expected.length == 0) {
+            throw new IllegalArgumentException("must supply at least one expected state");
+        }
+        for (int expect : expected) {
+            if (mState == expect) {
+                return;
+            }
+        }
+        final StringBuilder expectString = new StringBuilder(stateToString(expected[0]));
+        for (int i = 1; i < expected.length; i++) {
+            expectString.append(" or ").append(stateToString(expected[i]));
+        }
+        throw new IllegalStateException(caller + " called while fragment was "
+                + stateToString(mState) + "; expected " + expectString.toString());
+    }
+
+    public void checkStateAtLeast(String caller, int minState) {
+        if (mState < minState) {
+            throw new IllegalStateException(caller + " called while fragment was "
+                    + stateToString(mState) + "; expected at least " + stateToString(minState));
+        }
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mCalledOnAttach = true;
+        checkState("onAttach", DETACHED);
+        mState = ATTACHED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (mCalledOnCreate) {
+            throw new IllegalStateException("onCreate called more than once");
+        }
+        mCalledOnCreate = true;
+        checkState("onCreate", ATTACHED);
+        mState = CREATED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mCalledOnActivityCreated = true;
+        checkState("onActivityCreated", ATTACHED, CREATED);
+        mState = ACTIVITY_CREATED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mCalledOnStart = true;
+        checkState("onStart", ACTIVITY_CREATED);
+        mState = STARTED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mCalledOnResume = true;
+        checkState("onResume", STARTED);
+        mState = RESUMED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        mCalledOnSaveInstanceState = true;
+        checkGetActivity();
+        checkStateAtLeast("onSaveInstanceState", STARTED);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mCalledOnPause = true;
+        checkState("onPause", RESUMED);
+        mState = STARTED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mCalledOnStop = true;
+        checkState("onStop", STARTED);
+        mState = CREATED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCalledOnDestroy = true;
+        checkState("onDestroy", CREATED);
+        mState = ATTACHED;
+        checkGetActivity();
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mCalledOnDetach = true;
+        checkState("onDestroy", CREATED, ATTACHED);
+        mState = DETACHED;
+        checkGetActivity();
+    }
+}
diff --git a/v4/tests/java/android/support/v4/app/StrictViewFragment.java b/v4/tests/java/android/support/v4/app/StrictViewFragment.java
new file mode 100644
index 0000000..01251d2
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/StrictViewFragment.java
@@ -0,0 +1,59 @@
+/*
+ * 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.app;
+
+import android.support.v4.test.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class StrictViewFragment extends StrictFragment {
+    boolean mOnCreateViewCalled, mOnViewCreatedCalled, mOnDestroyViewCalled;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        checkGetActivity();
+        checkState("onCreateView", CREATED);
+        final View result = inflater.inflate(R.layout.strict_view_fragment, container, false);
+        mOnCreateViewCalled = true;
+        return result;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (view == null) {
+            throw new IllegalArgumentException("onViewCreated view argument should not be null");
+        }
+        checkGetActivity();
+        checkState("onViewCreated", CREATED);
+        mOnViewCreatedCalled = true;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (getView() == null) {
+            throw new IllegalStateException("getView returned null in onDestroyView");
+        }
+        checkGetActivity();
+        checkState("onDestroyView", CREATED);
+        mOnDestroyViewCalled = true;
+    }
+}
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java
similarity index 73%
rename from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
rename to v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java
index 8126b38..460d7d7 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
 
-import android.app.Activity;
+package android.support.v4.app.test;
 
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+import android.support.v4.app.FragmentActivity;
+
+public class EmptyFragmentTestActivity extends FragmentActivity {
 }
diff --git a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
index ca84498..06cf753 100644
--- a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
+++ b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
@@ -23,7 +23,6 @@
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
 import android.support.v4.test.R;
 import android.transition.Transition;
 import android.transition.Transition.TransitionListener;
@@ -32,6 +31,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * A simple activity used for Fragment Transitions and lifecycle event ordering
  */
@@ -55,20 +57,20 @@
         private static final String TRANSITION_KEY = "transition_";
         private int mLayoutId = R.layout.fragment_start;
         private final int[] mTransitionIds = new int[] {
-                android.R.transition.explode,
-                android.R.transition.explode,
-                android.R.transition.fade,
-                android.R.transition.fade,
-                android.R.transition.move,
-                android.R.transition.move,
+                R.transition.fade,
+                R.transition.fade,
+                R.transition.fade,
+                R.transition.fade,
+                R.transition.change_bounds,
+                R.transition.change_bounds,
         };
-        private final TransitionCalledListener[] mListeners =
-                new TransitionCalledListener[TRANSITION_COUNT];
-        private OnTransitionListener mOnTransitionListener;
+        private final Object[] mListeners = new Object[TRANSITION_COUNT];
 
         public TestFragment() {
-            for (int i = 0; i < TRANSITION_COUNT; i++) {
-                mListeners[i] = new TransitionCalledListener();
+            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+                for (int i = 0; i < TRANSITION_COUNT; i++) {
+                    mListeners[i] = new TransitionCalledListener();
+                }
             }
         }
 
@@ -86,8 +88,8 @@
 
         public void clearNotifications() {
             for (int i = 0; i < TRANSITION_COUNT; i++) {
-                mListeners[i].transitionStarted = false;
-                mListeners[i].transitionEnded = false;
+                ((TransitionCalledListener)mListeners[i]).startLatch = new CountDownLatch(1);
+                ((TransitionCalledListener)mListeners[i]).endLatch = new CountDownLatch(1);
             }
         }
 
@@ -133,16 +135,24 @@
             }
         }
 
-        public void setOnTransitionListener(OnTransitionListener listener) {
-            mOnTransitionListener = listener;
-        }
-
         public boolean wasStartCalled(int transitionKey) {
-            return mListeners[transitionKey].transitionStarted;
+            return ((TransitionCalledListener)mListeners[transitionKey]).startLatch.getCount() == 0;
         }
 
         public boolean wasEndCalled(int transitionKey) {
-            return mListeners[transitionKey].transitionEnded;
+            return ((TransitionCalledListener)mListeners[transitionKey]).endLatch.getCount() == 0;
+        }
+
+        public boolean waitForStart(int transitionKey)
+                throws InterruptedException {
+            TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
+            return l.startLatch.await(500,TimeUnit.MILLISECONDS);
+        }
+
+        public boolean waitForEnd(int transitionKey)
+                throws InterruptedException {
+            TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
+            return l.endLatch.await(500,TimeUnit.MILLISECONDS);
         }
 
         private Transition loadTransition(int key) {
@@ -151,33 +161,25 @@
                 return null;
             }
             Transition transition = TransitionInflater.from(getActivity()).inflateTransition(id);
-            transition.addListener(mListeners[key]);
+            transition.addListener(((TransitionCalledListener)mListeners[key]));
             return transition;
         }
 
-        private void notifyTransition() {
-            if (mOnTransitionListener != null) {
-                mOnTransitionListener.onTransition(this);
-            }
-        }
-
         private class TransitionCalledListener implements TransitionListener {
-            public boolean transitionStarted;
-            public boolean transitionEnded;
+            public CountDownLatch startLatch = new CountDownLatch(1);
+            public CountDownLatch endLatch = new CountDownLatch(1);
 
             public TransitionCalledListener() {
             }
 
             @Override
             public void onTransitionStart(Transition transition) {
-                transitionStarted = true;
-                notifyTransition();
+                startLatch.countDown();
             }
 
             @Override
             public void onTransitionEnd(Transition transition) {
-                transitionEnded = true;
-                notifyTransition();
+                endLatch.countDown();
             }
 
             @Override
@@ -194,26 +196,39 @@
         }
     }
 
-    public interface OnTransitionListener {
-        void onTransition(TestFragment fragment);
-    }
-
     public static class ParentFragment extends Fragment {
+        static final String CHILD_FRAGMENT_TAG = "childFragment";
         public boolean wasAttachedInTime;
-        public ChildFragment childFragment;
+
+        private boolean mRetainChild;
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            childFragment = new ChildFragment();
-            FragmentManager fm = getChildFragmentManager();
-            fm.beginTransaction().add(childFragment, "foo").commit();
-            fm.executePendingTransactions();
-            wasAttachedInTime = childFragment.attached;
+
+            ChildFragment f = getChildFragment();
+            if (f == null) {
+                f = new ChildFragment();
+                if (mRetainChild) {
+                    f.setRetainInstance(true);
+                }
+                getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
+            }
+            wasAttachedInTime = f.attached;
+        }
+
+        public ChildFragment getChildFragment() {
+            return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
+        }
+
+        public void setRetainChildInstance(boolean retainChild) {
+            mRetainChild = retainChild;
         }
     }
 
     public static class ChildFragment extends Fragment {
+        private OnAttachListener mOnAttachListener;
+
         public boolean attached;
         public boolean onActivityResultCalled;
         public int onActivityResultRequestCode;
@@ -223,6 +238,17 @@
         public void onAttach(Context activity) {
             super.onAttach(activity);
             attached = true;
+            if (mOnAttachListener != null) {
+                mOnAttachListener.onAttach(activity, this);
+            }
+        }
+
+        public void setOnAttachListener(OnAttachListener listener) {
+            mOnAttachListener = listener;
+        }
+
+        public interface OnAttachListener {
+            void onAttach(Context activity, ChildFragment fragment);
         }
 
         @Override
diff --git a/v4/tests/java/android/support/v4/content/ContextCompatTest.java b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
index e2fc0b7..cccf892 100644
--- a/v4/tests/java/android/support/v4/content/ContextCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
@@ -15,51 +15,52 @@
  */
 package android.support.v4.content;
 
-import android.content.res.ColorStateList;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.v4.BaseInstrumentationTestCase;
 import android.support.v4.ThemedYellowActivity;
-import android.support.v4.content.ContextCompat;
 import android.support.v4.test.R;
 import android.support.v4.testutils.TestUtils;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.TypedValue;
+import org.junit.Before;
+import org.junit.Test;
 
-public class ContextCompatTest extends ActivityInstrumentationTestCase2<ThemedYellowActivity> {
-    private static final String TAG = "ResourcesCompatTest";
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class ContextCompatTest extends BaseInstrumentationTestCase<ThemedYellowActivity> {
+    private Context mContext;
 
     public ContextCompatTest() {
-        super("android.support.v4.content", ThemedYellowActivity.class);
+        super(ThemedYellowActivity.class);
     }
 
-    @UiThreadTest
-    @SmallTest
-    public void testGetColor() throws Throwable {
-        Context context = getActivity();
+    @Before
+    public void setup() {
+        mContext = mActivityTestRule.getActivity();
+    }
 
+    @Test
+    public void testGetColor() throws Throwable {
         assertEquals("Unthemed color load", 0xFFFF8090,
-                ContextCompat.getColor(context, R.color.text_color));
+                ContextCompat.getColor(mContext, R.color.text_color));
 
         if (Build.VERSION.SDK_INT >= 23) {
             // The following test is only expected to pass on v23+ devices. The result of
             // calling theme-aware getColor() in pre-v23 is undefined.
             assertEquals("Themed yellow color load",
-                    ContextCompat.getColor(context, R.color.simple_themed_selector),
+                    ContextCompat.getColor(mContext, R.color.simple_themed_selector),
                     0xFFF0B000);
         }
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetColorStateList() throws Throwable {
-        Context context = getActivity();
-
         ColorStateList unthemedColorStateList =
-                ContextCompat.getColorStateList(context, R.color.complex_unthemed_selector);
+                ContextCompat.getColorStateList(mContext, R.color.complex_unthemed_selector);
         assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
                 unthemedColorStateList.getDefaultColor());
         assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
@@ -73,7 +74,7 @@
             // The following tests are only expected to pass on v23+ devices. The result of
             // calling theme-aware getColorStateList() in pre-v23 is undefined.
             ColorStateList themedYellowColorStateList =
-                    ContextCompat.getColorStateList(context, R.color.complex_themed_selector);
+                    ContextCompat.getColorStateList(mContext, R.color.complex_themed_selector);
             assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
                     themedYellowColorStateList.getDefaultColor());
             assertEquals("Themed yellow color state list load: focused", 0xFFF0A020,
@@ -85,45 +86,35 @@
         }
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetDrawable() throws Throwable {
-        Context context = getActivity();
-
         Drawable unthemedDrawable =
-                ContextCompat.getDrawable(context, R.drawable.test_drawable_red);
+                ContextCompat.getDrawable(mContext, R.drawable.test_drawable_red);
         TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
-                unthemedDrawable, context.getResources().getColor(R.color.test_red));
+                unthemedDrawable, mContext.getResources().getColor(R.color.test_red));
 
         if (Build.VERSION.SDK_INT >= 23) {
             // The following test is only expected to pass on v23+ devices. The result of
             // calling theme-aware getDrawable() in pre-v23 is undefined.
             Drawable themedYellowDrawable =
-                    ContextCompat.getDrawable(context, R.drawable.themed_drawable);
+                    ContextCompat.getDrawable(mContext, R.drawable.themed_drawable);
             TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
                     themedYellowDrawable, 0xFFF0B000);
         }
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testCheckSelfPermissionNull() {
+        ContextCompat.checkSelfPermission(mContext, null);
+    }
 
-    @UiThreadTest
-    @SmallTest
-    public void testCheckSelfPermission() throws Throwable {
-        Context context = getActivity();
-
-        try {
-            ContextCompat.checkSelfPermission(context, null);
-            fail("Should have thrown an exception on null parameter");
-        } catch (IllegalArgumentException iae) {
-            // This is the expected condition - just ignore and continue with the rest of the
-            // tests in this method.
-        }
-
+    @Test
+    public void testCheckSelfPermission() {
         assertEquals("Vibrate permission granted", PackageManager.PERMISSION_GRANTED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.VIBRATE));
         assertEquals("Wake lock permission granted", PackageManager.PERMISSION_GRANTED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.WAKE_LOCK));
 
         if (Build.VERSION.SDK_INT >= 23) {
@@ -132,34 +123,34 @@
             // to apps targeting SDK 23 even if those are defined in the manifest.
             // This is why the following permissions are expected to be denied.
             assertEquals("Read contacts permission granted", PackageManager.PERMISSION_DENIED,
-                    ContextCompat.checkSelfPermission(context,
+                    ContextCompat.checkSelfPermission(mContext,
                             android.Manifest.permission.READ_CONTACTS));
             assertEquals("Write contacts permission granted", PackageManager.PERMISSION_DENIED,
-                    ContextCompat.checkSelfPermission(context,
+                    ContextCompat.checkSelfPermission(mContext,
                             android.Manifest.permission.WRITE_CONTACTS));
         } else {
             // And on older devices declared dangerous permissions are expected to be granted.
             assertEquals("Read contacts permission denied", PackageManager.PERMISSION_GRANTED,
-                    ContextCompat.checkSelfPermission(context,
+                    ContextCompat.checkSelfPermission(mContext,
                             android.Manifest.permission.READ_CONTACTS));
             assertEquals("Write contacts permission denied", PackageManager.PERMISSION_GRANTED,
-                    ContextCompat.checkSelfPermission(context,
+                    ContextCompat.checkSelfPermission(mContext,
                             android.Manifest.permission.WRITE_CONTACTS));
         }
 
         // The following permissions (normal and dangerous) are expected to be denied as they are
         // not declared in our manifest.
         assertEquals("Access network state permission denied", PackageManager.PERMISSION_DENIED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.ACCESS_NETWORK_STATE));
         assertEquals("Bluetooth permission denied", PackageManager.PERMISSION_DENIED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.BLUETOOTH));
         assertEquals("Call phone permission denied", PackageManager.PERMISSION_DENIED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.CALL_PHONE));
         assertEquals("Delete packages permission denied", PackageManager.PERMISSION_DENIED,
-                ContextCompat.checkSelfPermission(context,
+                ContextCompat.checkSelfPermission(mContext,
                         android.Manifest.permission.DELETE_PACKAGES));
     }
 }
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
index 4efca5d..19d5609 100644
--- a/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
@@ -19,53 +19,53 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.support.v4.content.res.ResourcesCompat;
+import android.support.test.InstrumentationRegistry;
 import android.support.v4.test.R;
 import android.support.v4.testutils.TestUtils;
-import android.support.v4.widget.TestActivity;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.DisplayMetrics;
-import android.util.TypedValue;
+import org.junit.Before;
+import org.junit.Test;
 
-public class ResourcesCompatTest extends ActivityInstrumentationTestCase2<TestActivity> {
-    private static final String TAG = "ResourcesCompatTest";
+import static org.junit.Assert.assertEquals;
 
-    public ResourcesCompatTest() {
-        super("android.support.v4.content.res", TestActivity.class);
+@SmallTest
+public class ResourcesCompatTest {
+    private Resources mResources;
+
+    @Before
+    public void setup() {
+        mResources = InstrumentationRegistry.getContext().getResources();
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetColor() throws Throwable {
-        final Resources res = getActivity().getResources();
         assertEquals("Unthemed color load",
-                ResourcesCompat.getColor(res, R.color.text_color, null),
+                ResourcesCompat.getColor(mResources, R.color.text_color, null),
                 0xFFFF8090);
 
         if (Build.VERSION.SDK_INT >= 23) {
             // The following tests are only expected to pass on v23+ devices. The result of
             // calling theme-aware getColor() in pre-v23 is undefined.
-            final Resources.Theme yellowTheme = res.newTheme();
+            final Resources.Theme yellowTheme = mResources.newTheme();
             yellowTheme.applyStyle(R.style.YellowTheme, true);
             assertEquals("Themed yellow color load", 0xFFF0B000,
-                    ResourcesCompat.getColor(res, R.color.simple_themed_selector, yellowTheme));
+                    ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+                            yellowTheme));
 
-            final Resources.Theme lilacTheme = res.newTheme();
+            final Resources.Theme lilacTheme = mResources.newTheme();
             lilacTheme.applyStyle(R.style.LilacTheme, true);
             assertEquals("Themed lilac color load", 0xFFF080F0,
-                    ResourcesCompat.getColor(res, R.color.simple_themed_selector, lilacTheme));
+                    ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+                            lilacTheme));
         }
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetColorStateList() throws Throwable {
-        final Resources res = getActivity().getResources();
-
         final ColorStateList unthemedColorStateList =
-                ResourcesCompat.getColorStateList(res, R.color.complex_unthemed_selector, null);
+                ResourcesCompat.getColorStateList(mResources, R.color.complex_unthemed_selector,
+                        null);
         assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
                 unthemedColorStateList.getDefaultColor());
         assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
@@ -78,10 +78,10 @@
         if (Build.VERSION.SDK_INT >= 23) {
             // The following tests are only expected to pass on v23+ devices. The result of
             // calling theme-aware getColorStateList() in pre-v23 is undefined.
-            final Resources.Theme yellowTheme = res.newTheme();
+            final Resources.Theme yellowTheme = mResources.newTheme();
             yellowTheme.applyStyle(R.style.YellowTheme, true);
             final ColorStateList themedYellowColorStateList =
-                    ResourcesCompat.getColorStateList(res, R.color.complex_themed_selector,
+                    ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
                             yellowTheme);
             assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
                     themedYellowColorStateList.getDefaultColor());
@@ -92,10 +92,10 @@
                     themedYellowColorStateList.getColorForState(
                             new int[]{android.R.attr.state_pressed}, 0));
 
-            final Resources.Theme lilacTheme = res.newTheme();
+            final Resources.Theme lilacTheme = mResources.newTheme();
             lilacTheme.applyStyle(R.style.LilacTheme, true);
             final ColorStateList themedLilacColorStateList =
-                    ResourcesCompat.getColorStateList(res, R.color.complex_themed_selector,
+                    ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
                             lilacTheme);
             assertEquals("Themed lilac color state list load: default", 0xFFF080F0,
                     themedLilacColorStateList.getDefaultColor());
@@ -108,43 +108,37 @@
         }
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetDrawable() throws Throwable {
-        final Resources res = getActivity().getResources();
-
         final Drawable unthemedDrawable =
-                ResourcesCompat.getDrawable(res, R.drawable.test_drawable_red, null);
+                ResourcesCompat.getDrawable(mResources, R.drawable.test_drawable_red, null);
         TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
-                unthemedDrawable, res.getColor(R.color.test_red));
+                unthemedDrawable, mResources.getColor(R.color.test_red));
 
         if (Build.VERSION.SDK_INT >= 23) {
             // The following tests are only expected to pass on v23+ devices. The result of
             // calling theme-aware getDrawable() in pre-v23 is undefined.
-            final Resources.Theme yellowTheme = res.newTheme();
+            final Resources.Theme yellowTheme = mResources.newTheme();
             yellowTheme.applyStyle(R.style.YellowTheme, true);
             final Drawable themedYellowDrawable =
-                    ResourcesCompat.getDrawable(res, R.drawable.themed_drawable, yellowTheme);
+                    ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable,
+                            yellowTheme);
             TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
                     themedYellowDrawable, 0xFFF0B000);
 
-            final Resources.Theme lilacTheme = res.newTheme();
+            final Resources.Theme lilacTheme = mResources.newTheme();
             lilacTheme.applyStyle(R.style.LilacTheme, true);
             final Drawable themedLilacDrawable =
-                    ResourcesCompat.getDrawable(res, R.drawable.themed_drawable, lilacTheme);
+                    ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable, lilacTheme);
             TestUtils.assertAllPixelsOfColor("Themed lilac drawable load",
                     themedLilacDrawable, 0xFFF080F0);
         }
     }
 
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetDrawableForDensityUnthemed() throws Throwable {
         // Density-aware drawable loading for now only works on raw bitmap drawables.
 
-        final Resources res = getActivity().getResources();
-        final DisplayMetrics metrics = res.getDisplayMetrics();
-
         // Different variants of density_aware_drawable are set up in the following way:
         //    mdpi - 12x12 px which is 12x12 dip
         //    hdpi - 21x21 px which is 14x14 dip
@@ -155,13 +149,13 @@
         // dimensions.
 
         final Drawable unthemedDrawableForMediumDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_MEDIUM, null);
         // For pre-v15 devices we should get a drawable that corresponds to the density of the
         // current device. For v15+ devices we should get a drawable that corresponds to the
         // density requested in the API call.
         final int expectedSizeForMediumDensity = (Build.VERSION.SDK_INT < 15) ?
-                res.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
+                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
         assertEquals("Unthemed density-aware drawable load: medium width",
                 expectedSizeForMediumDensity, unthemedDrawableForMediumDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: medium height",
@@ -169,39 +163,39 @@
                 unthemedDrawableForMediumDensity.getIntrinsicHeight());
 
         final Drawable unthemedDrawableForHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_HIGH, null);
         // For pre-v15 devices we should get a drawable that corresponds to the density of the
         // current device. For v15+ devices we should get a drawable that corresponds to the
         // density requested in the API call.
         final int expectedSizeForHighDensity = (Build.VERSION.SDK_INT < 15) ?
-                res.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
+                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
         assertEquals("Unthemed density-aware drawable load: high width",
                 expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: high height",
                 expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicHeight());
 
         final Drawable unthemedDrawableForXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_XHIGH, null);
         // For pre-v15 devices we should get a drawable that corresponds to the density of the
         // current device. For v15+ devices we should get a drawable that corresponds to the
         // density requested in the API call.
         final int expectedSizeForXHighDensity = (Build.VERSION.SDK_INT < 15) ?
-                res.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
+                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
         assertEquals("Unthemed density-aware drawable load: xhigh width",
                 expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: xhigh height",
                 expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicHeight());
 
         final Drawable unthemedDrawableForXXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.density_aware_drawable,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
                         DisplayMetrics.DENSITY_XXHIGH, null);
         // For pre-v15 devices we should get a drawable that corresponds to the density of the
         // current device. For v15+ devices we should get a drawable that corresponds to the
         // density requested in the API call.
         final int expectedSizeForXXHighDensity = (Build.VERSION.SDK_INT < 15) ?
-                res.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
+                mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
         assertEquals("Unthemed density-aware drawable load: xxhigh width",
                 expectedSizeForXXHighDensity, unthemedDrawableForXXHighDensity.getIntrinsicWidth());
         assertEquals("Unthemed density-aware drawable load: xxhigh height",
@@ -209,9 +203,7 @@
                 unthemedDrawableForXXHighDensity.getIntrinsicHeight());
     }
 
-
-    @UiThreadTest
-    @SmallTest
+    @Test
     public void testGetDrawableForDensityThemed() throws Throwable {
         if (Build.VERSION.SDK_INT < 21) {
             // The following tests are only expected to pass on v21+ devices. The result of
@@ -222,67 +214,64 @@
         // Density- and theme-aware drawable loading for now only works partially. This test
         // checks only for theming of a tinted bitmap XML drawable, but not correct scaling.
 
-        final Resources res = getActivity().getResources();
-        final DisplayMetrics metrics = res.getDisplayMetrics();
-
         // Set up the two test themes, yellow and lilac.
-        final Resources.Theme yellowTheme = res.newTheme();
+        final Resources.Theme yellowTheme = mResources.newTheme();
         yellowTheme.applyStyle(R.style.YellowTheme, true);
 
-        final Resources.Theme lilacTheme = res.newTheme();
+        final Resources.Theme lilacTheme = mResources.newTheme();
         lilacTheme.applyStyle(R.style.LilacTheme, true);
 
         Drawable themedYellowDrawableForMediumDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_MEDIUM, yellowTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : medium color",
                 themedYellowDrawableForMediumDensity, 0xFFF0B000);
 
         Drawable themedLilacDrawableForMediumDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_MEDIUM, lilacTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : medium color",
                 themedLilacDrawableForMediumDensity, 0xFFF080F0);
 
         Drawable themedYellowDrawableForHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_HIGH, yellowTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : high color",
                 themedYellowDrawableForHighDensity, 0xFFF0B000);
 
         Drawable themedLilacDrawableForHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_HIGH, lilacTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : high color",
                 themedLilacDrawableForHighDensity, 0xFFF080F0);
 
         Drawable themedYellowDrawableForXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_XHIGH, yellowTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xhigh color",
                 themedYellowDrawableForXHighDensity, 0xFFF0B000);
 
         Drawable themedLilacDrawableForXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_XHIGH, lilacTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xhigh color",
                 themedLilacDrawableForXHighDensity, 0xFFF080F0);
 
         Drawable themedYellowDrawableForXXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_XXHIGH, yellowTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xxhigh color",
                 themedYellowDrawableForXXHighDensity, 0xFFF0B000);
 
         Drawable themedLilacDrawableForXXHighDensity =
-                ResourcesCompat.getDrawableForDensity(res, R.drawable.themed_bitmap,
+                ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
                         DisplayMetrics.DENSITY_XXHIGH, lilacTheme);
         // We should get a drawable that corresponds to the theme requested in the API call.
         TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xxhigh color",
diff --git a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
index b25ed4f..998980b 100644
--- a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
+++ b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
@@ -17,17 +17,20 @@
 package android.support.v4.graphics;
 
 import android.graphics.Color;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.lang.Integer;
 import java.util.ArrayList;
 
-/**
- * @hide
- */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class ColorUtilsTest extends AndroidTestCase {
+public class ColorUtilsTest {
 
     // 0.5% of the max value
     private static final float ALLOWED_OFFSET_HUE = 360 * 0.005f;
@@ -81,16 +84,18 @@
 
     public void testColorToHSL() {
         for (TestEntry entry : sEntryList) {
-            testColorToHSL(entry.rgb, entry.hsl);
+            verifyColorToHSL(entry.rgb, entry.hsl);
         }
     }
 
+    @Test
     public void testHSLToColor() {
         for (TestEntry entry : sEntryList) {
-            testHSLToColor(entry.hsl, entry.rgb);
+            verifyHSLToColor(entry.hsl, entry.rgb);
         }
     }
 
+    @Test
     public void testColorToHslLimits() {
         final float[] hsl = new float[3];
 
@@ -103,68 +108,77 @@
         }
     }
 
+    @Test
     public void testColorToXYZ() {
         for (TestEntry entry : sEntryList) {
-            testColorToXYZ(entry.rgb, entry.xyz);
+            verifyColorToXYZ(entry.rgb, entry.xyz);
         }
     }
 
+    @Test
     public void testColorToLAB() {
         for (TestEntry entry : sEntryList) {
-            testColorToLAB(entry.rgb, entry.lab);
+            verifyColorToLAB(entry.rgb, entry.lab);
         }
     }
 
+    @Test
     public void testLABToXYZ() {
         for (TestEntry entry : sEntryList) {
-            testLABToXYZ(entry.lab, entry.xyz);
+            verifyLABToXYZ(entry.lab, entry.xyz);
         }
     }
 
+    @Test
     public void testXYZToColor() {
         for (TestEntry entry : sEntryList) {
-            testXYZToColor(entry.xyz, entry.rgb);
+            verifyXYZToColor(entry.xyz, entry.rgb);
         }
     }
 
+    @Test
     public void testLABToColor() {
         for (TestEntry entry : sEntryList) {
-            testLABToColor(entry.lab, entry.rgb);
+            verifyLABToColor(entry.lab, entry.rgb);
         }
     }
 
+    @Test
     public void testMinAlphas() {
         for (TestEntry entry : sEntryList) {
-            testMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
+            verifyMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
                     ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 3.0f));
-            testMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
+            verifyMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
                     ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 4.5f));
-            testMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
+            verifyMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
                     ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 3.0f));
-            testMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
+            verifyMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
                     ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 4.5f));
         }
     }
 
+    @Test
     public void testCircularInterpolationForwards() {
-        assertEquals(0f, ColorUtils.circularInterpolate(0, 180, 0f));
-        assertEquals(90f, ColorUtils.circularInterpolate(0, 180, 0.5f));
-        assertEquals(180f, ColorUtils.circularInterpolate(0, 180, 1f));
+        assertEquals(0f, ColorUtils.circularInterpolate(0, 180, 0f), 0f);
+        assertEquals(90f, ColorUtils.circularInterpolate(0, 180, 0.5f), 0f);
+        assertEquals(180f, ColorUtils.circularInterpolate(0, 180, 1f), 0f);
     }
 
+    @Test
     public void testCircularInterpolationBackwards() {
-        assertEquals(180f, ColorUtils.circularInterpolate(180, 0, 0f));
-        assertEquals(90f, ColorUtils.circularInterpolate(180, 0, 0.5f));
-        assertEquals(0f, ColorUtils.circularInterpolate(180, 0, 1f));
+        assertEquals(180f, ColorUtils.circularInterpolate(180, 0, 0f), 0f);
+        assertEquals(90f, ColorUtils.circularInterpolate(180, 0, 0.5f), 0f);
+        assertEquals(0f, ColorUtils.circularInterpolate(180, 0, 1f), 0f);
     }
 
+    @Test
     public void testCircularInterpolationCrossZero() {
-        assertEquals(270f, ColorUtils.circularInterpolate(270, 90, 0f));
-        assertEquals(180f, ColorUtils.circularInterpolate(270, 90, 0.5f));
-        assertEquals(90f, ColorUtils.circularInterpolate(270, 90, 1f));
+        assertEquals(270f, ColorUtils.circularInterpolate(270, 90, 0f), 0f);
+        assertEquals(180f, ColorUtils.circularInterpolate(270, 90, 0.5f), 0f);
+        assertEquals(90f, ColorUtils.circularInterpolate(270, 90, 1f), 0f);
     }
 
-    private static void testMinAlpha(String title, int color, float expected, int actual) {
+    private static void verifyMinAlpha(String title, int color, float expected, int actual) {
         final String message = title + " text within error for #" + Integer.toHexString(color);
         if (expected < 0) {
             assertEquals(message, actual, -1);
@@ -173,7 +187,7 @@
         }
     }
 
-    private static void testColorToHSL(int color, float[] expected) {
+    private static void verifyColorToHSL(int color, float[] expected) {
         float[] actualHSL = new float[3];
         ColorUtils.colorToHSL(color, actualHSL);
 
@@ -185,7 +199,7 @@
                 ALLOWED_OFFSET_LIGHTNESS);
     }
 
-    private static void testHSLToColor(float[] hsl, int expected) {
+    private static void verifyHSLToColor(float[] hsl, int expected) {
         final int actualRgb = ColorUtils.HSLToColor(hsl);
 
         assertEquals("Red not within offset", Color.red(expected), Color.red(actualRgb),
@@ -196,7 +210,7 @@
                 ALLOWED_OFFSET_RGB_COMPONENT);
     }
 
-    private static void testColorToLAB(int color, double[] expected) {
+    private static void verifyColorToLAB(int color, double[] expected) {
         double[] result = new double[3];
         ColorUtils.colorToLAB(color, result);
 
@@ -205,7 +219,7 @@
         assertEquals("B not within offset", expected[2], result[2], ALLOWED_OFFSET_LAB);
     }
 
-    private static void testColorToXYZ(int color, double[] expected) {
+    private static void verifyColorToXYZ(int color, double[] expected) {
         double[] result = new double[3];
         ColorUtils.colorToXYZ(color, result);
 
@@ -214,7 +228,7 @@
         assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
     }
 
-    private static void testLABToXYZ(double[] lab, double[] expected) {
+    private static void verifyLABToXYZ(double[] lab, double[] expected) {
         double[] result = new double[3];
         ColorUtils.LABToXYZ(lab[0], lab[1], lab[2], result);
 
@@ -223,17 +237,17 @@
         assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
     }
 
-    private static void testXYZToColor(double[] xyz, int expected) {
+    private static void verifyXYZToColor(double[] xyz, int expected) {
         final int result = ColorUtils.XYZToColor(xyz[0], xyz[1], xyz[2]);
-        assertRGBComponentsClose(expected, result);
+        verifyRGBComponentsClose(expected, result);
     }
 
-    private static void testLABToColor(double[] lab, int expected) {
+    private static void verifyLABToColor(double[] lab, int expected) {
         final int result = ColorUtils.LABToColor(lab[0], lab[1], lab[2]);
-        assertRGBComponentsClose(expected, result);
+        verifyRGBComponentsClose(expected, result);
     }
 
-    private static void assertRGBComponentsClose(int expected, int actual) {
+    private static void verifyRGBComponentsClose(int expected, int actual) {
         final String message = "Expected: #" + Integer.toHexString(expected)
                 + ", Actual: #" + Integer.toHexString(actual);
         assertEquals("R not equal: " + message, Color.red(expected), Color.red(actual), 1);
diff --git a/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
index 8409296..abb357c 100644
--- a/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
+++ b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
@@ -19,22 +19,29 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.*;
 
-public class DrawableCompatTest extends AndroidTestCase {
-
-    @SmallTest
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DrawableCompatTest {
+    @Test
     public void testDrawableUnwrap() {
         final Drawable original = new GradientDrawable();
         final Drawable wrappedDrawable = DrawableCompat.wrap(original);
         assertSame(original, DrawableCompat.unwrap(wrappedDrawable));
     }
 
-    @SmallTest
+    @Test
     public void testDrawableChangeBoundsCopy() {
         final Rect bounds = new Rect(0, 0, 10, 10);
 
@@ -47,13 +54,13 @@
         assertEquals(bounds, drawable.getBounds());
     }
 
-    @SmallTest
+    @Test
     public void testDrawableWrapOnlyWrapsOnce() {
         final Drawable wrappedDrawable = DrawableCompat.wrap(new GradientDrawable());
         assertSame(wrappedDrawable, DrawableCompat.wrap(wrappedDrawable));
     }
 
-    @SmallTest
+    @Test
     public void testWrapMutatedDrawableHasConstantState() {
         // First create a Drawable, and mutated it so that it has a constant state
         Drawable drawable = new GradientDrawable();
@@ -65,7 +72,7 @@
         assertNotNull(wrapper.getConstantState());
     }
 
-    @SmallTest
+    @Test
     public void testWrappedDrawableHasCallbackSet() {
         // First create a Drawable
         final Drawable drawable = new GradientDrawable();
@@ -81,5 +88,4 @@
         // ...and verify that the wrapper calls to be invalidated
         verify(mockCallback, times(1)).invalidateDrawable(wrapper);
     }
-
 }
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
index 1d8ef7a..6dc2042 100644
--- a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
+++ b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
@@ -16,14 +16,19 @@
 
 package android.support.v4.text;
 
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Locale;
 
-/** @hide */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class BidiFormatterTest extends AndroidTestCase {
+public class BidiFormatterTest {
 
     private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
     private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
@@ -42,6 +47,7 @@
     private static final String RLE = "\u202B";
     private static final String PDF = "\u202C";
 
+    @Test
     public void testIsRtlContext() {
         assertEquals(false, LTR_FMT.isRtlContext());
         assertEquals(true, RTL_FMT.isRtlContext());
@@ -50,11 +56,13 @@
         assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
     }
 
+    @Test
     public void testBuilderIsRtlContext() {
         assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
         assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
     }
 
+    @Test
     public void testIsRtl() {
         assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
         assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
@@ -63,6 +71,7 @@
         assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
     }
 
+    @Test
     public void testUnicodeWrap() {
         // Make sure an input of null doesn't crash anything.
         assertNull(LTR_FMT.unicodeWrap(null));
@@ -148,14 +157,16 @@
                 LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
         assertEquals("entry and exit dir opposite to LTR context, no isolation",
                 HE + EN + HE,
-                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+                LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+                        false));
 
         assertEquals("entry and exit dir opposite to RTL context",
                 EN + HE + EN + RLM,
                 RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
         assertEquals("entry and exit dir opposite to RTL context, no isolation",
                 EN + HE + EN,
-                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+                RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+                        false));
 
         // Entry and exit directionality matching context, but with opposite overall directionality.
         assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
@@ -166,7 +177,8 @@
                 LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
         assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
                 RLE + EN + HE + EN + PDF,
-                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+                LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+                        false));
 
         assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
                 LRE + HE + EN + HE + PDF + RLM,
@@ -176,6 +188,7 @@
                 RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
         assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
                 LRE + HE + EN + HE + PDF,
-                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+                RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+                        false));
     }
 }
diff --git a/v4/tests/java/android/support/v4/view/GravityCompatTest.java b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
index 2e2f180..36e557a 100644
--- a/v4/tests/java/android/support/v4/view/GravityCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
@@ -17,13 +17,20 @@
 
 import android.graphics.Rect;
 import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.testutils.TestUtils;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.Gravity;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class GravityCompatTest extends AndroidTestCase {
-    @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GravityCompatTest {
+    @Test
     public void testConstants() {
         // Compat constants must match core constants since they can be OR'd with
         // other core constants.
@@ -31,7 +38,7 @@
         assertEquals("End constants", Gravity.END, GravityCompat.END);
     }
 
-    @SmallTest
+    @Test
     public void testGetAbsoluteGravity() {
         assertEquals("Left under LTR",
                 GravityCompat.getAbsoluteGravity(Gravity.LEFT, ViewCompat.LAYOUT_DIRECTION_LTR),
@@ -78,7 +85,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testApplyNoOffsetsLtr() {
         Rect outRect = new Rect();
 
@@ -176,7 +183,7 @@
                 outRect, 100, 50, 200, 100);
     }
 
-    @SmallTest
+    @Test
     public void testApplyNoOffsetsRtl() {
         Rect outRect = new Rect();
 
diff --git a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
index 057db99..48d6dab 100644
--- a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
@@ -16,12 +16,19 @@
 package android.support.v4.view;
 
 import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.ViewGroup;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class MarginLayoutParamsCompatTest extends AndroidTestCase {
-    @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MarginLayoutParamsCompatTest {
+    @Test
     public void testLayoutDirection() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -43,8 +50,7 @@
                 MarginLayoutParamsCompat.getLayoutDirection(mlp));
     }
 
-
-    @SmallTest
+    @Test
     public void testMappingOldMarginsToNewMarginsLtr() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -57,7 +63,7 @@
                 MarginLayoutParamsCompat.getMarginEnd(mlp));
     }
 
-    @SmallTest
+    @Test
     public void testMappingOldMarginsToNewMarginsRtl() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -79,7 +85,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testMappingNewMarginsToNewMarginsLtr() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -98,7 +104,7 @@
                 MarginLayoutParamsCompat.getMarginStart(mlp));
     }
 
-    @SmallTest
+    @Test
     public void testMappingNewMarginsToNewMarginsRtl() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -123,7 +129,7 @@
                 MarginLayoutParamsCompat.getMarginStart(mlp));
     }
 
-    @SmallTest
+    @Test
     public void testResolveMarginsLtr() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
@@ -142,7 +148,7 @@
         assertEquals("Keeping left margin field under LTR", 50, mlp.leftMargin);
     }
 
-    @SmallTest
+    @Test
     public void testResolveMarginsRtl() {
         ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
 
diff --git a/v4/tests/java/android/support/v4/view/ViewCompatTest.java b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
index 29a0f5a..c4217a5 100644
--- a/v4/tests/java/android/support/v4/view/ViewCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
@@ -15,12 +15,19 @@
  */
 package android.support.v4.view;
 
+import android.support.test.runner.AndroidJUnit4;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class ViewCompatTest extends AndroidTestCase {
-    @SmallTest
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ViewCompatTest {
+    @Test
     public void testConstants() {
         // Compat constants must match core constants since they can be used interchangeably
         // in various support lib calls.
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
index 13a78dc..a2ac8bd 100644
--- a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -31,10 +31,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+@MediumTest
 public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
 
     private View mView;
-    private int mVariable;
     private int mNumListenerCalls = 0;
 
     public ViewPropertyAnimatorCompatTest() {
@@ -42,13 +42,12 @@
     }
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         final Activity activity = mActivityTestRule.getActivity();
         mView = activity.findViewById(R.id.view);
     }
 
     @Test
-    @MediumTest
     public void testWithEndAction() throws Throwable {
         final CountDownLatch latch1 = new CountDownLatch(1);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@@ -87,7 +86,6 @@
     }
 
     @Test
-    @MediumTest
     public void testWithStartAction() throws Throwable {
         final CountDownLatch latch1 = new CountDownLatch(1);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
diff --git a/v4/tests/java/android/support/v4/view/VpaActivity.java b/v4/tests/java/android/support/v4/view/VpaActivity.java
index d84d62d..64d698a 100644
--- a/v4/tests/java/android/support/v4/view/VpaActivity.java
+++ b/v4/tests/java/android/support/v4/view/VpaActivity.java
@@ -16,14 +16,12 @@
 
 package android.support.v4.view;
 
-import android.app.Activity;
+import android.support.v4.BaseTestActivity;
 import android.support.v4.test.R;
-import android.os.Bundle;
 
-public class VpaActivity extends Activity {
+public class VpaActivity extends BaseTestActivity {
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.vpa_activity);
+    protected int getContentViewLayoutResId() {
+        return R.layout.vpa_activity;
     }
 }
diff --git a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
index 42d82e9..db4a497 100644
--- a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
@@ -15,14 +15,8 @@
  */
 package android.support.v4.widget;
 
-import android.os.Build;
-
-/**
- * @hide
- */
 public class DonutScrollerCompatTest extends ScrollerCompatTestBase {
-
     public DonutScrollerCompatTest() {
-        super(Build.VERSION_CODES.DONUT);
+        super(4);
     }
 }
diff --git a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
index c777808..e947749 100644
--- a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
@@ -15,11 +15,7 @@
  */
 package android.support.v4.widget;
 
-/**
- * @hide
- */
 public class GingerbreadScrollerCompatTest extends ScrollerCompatTestBase {
-
     public GingerbreadScrollerCompatTest() {
         super(9);
     }
diff --git a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
index acbc89d..5d8a16d 100644
--- a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
@@ -15,13 +15,7 @@
  */
 package android.support.v4.widget;
 
-import android.os.Build;
-
-/**
- * @hide
- */
 public class IcsScrollerCompatTest extends ScrollerCompatTestBase {
-
     public IcsScrollerCompatTest() {
         super(14);
     }
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
index 2b83fc2..7b7c9f5 100644
--- a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -16,22 +16,28 @@
 package android.support.v4.widget;
 
 import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 
-/**
- * @hide
- */
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
 @MediumTest
-abstract public class ScrollerCompatTestBase extends AndroidTestCase {
+public abstract class ScrollerCompatTestBase {
 
     private static final boolean DEBUG = false;
 
@@ -52,9 +58,11 @@
         Constructor<ScrollerCompat> constructor = ScrollerCompat.class
                 .getDeclaredConstructor(int.class, Context.class, Interpolator.class);
         constructor.setAccessible(true);
-        mScroller = constructor.newInstance(mApiLevel, getContext(), interpolator);
+        mScroller = constructor.newInstance(mApiLevel, InstrumentationRegistry.getContext(),
+                interpolator);
     }
 
+    @Test
     public void testTargetReached() throws Throwable {
         if (DEBUG) {
             Log.d(TAG, "testing if target is reached");
@@ -70,6 +78,7 @@
                 mScroller.getCurrY());
     }
 
+    @Test
     public void testAbort() throws Throwable {
         if (DEBUG) {
             Log.d(TAG, "testing abort");
diff --git a/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java
new file mode 100644
index 0000000..27b7be6
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java
@@ -0,0 +1,77 @@
+/*
+ * 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.widget;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+
+public class SwipeRefreshLayoutActions {
+    public static ViewAction setRefreshing() {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(SwipeRefreshLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set SwipeRefreshLayout refreshing state";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view;
+                swipeRefreshLayout.setRefreshing(true);
+
+                // Intentionally not waiting until idle here because it will not become idle due to
+                // the animation.
+            }
+        };
+    }
+
+    public static ViewAction setSize(final int size) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(SwipeRefreshLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set SwipeRefreshLayout size";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view;
+                swipeRefreshLayout.setSize(size);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java
similarity index 64%
copy from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
copy to v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java
index 124d683..6f30aa2 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java
@@ -14,21 +14,14 @@
  * limitations under the License.
  */
 
-package android.support.design.widget;
+package android.support.v4.widget;
 
-import android.support.design.test.R;
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
 
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+public class SwipeRefreshLayoutActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.test_design_snackbar;
+        return R.layout.swipe_refresh_layout_activity;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
-}
\ No newline at end of file
+}
diff --git a/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
new file mode 100644
index 0000000..fca1577
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v4.widget.SwipeRefreshLayoutActions.setRefreshing;
+import static android.support.v4.widget.SwipeRefreshLayoutActions.setSize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests SwipeRefreshLayout widget.
+ */
+public class SwipeRefreshLayoutTest
+        extends BaseInstrumentationTestCase<SwipeRefreshLayoutActivity> {
+
+    private SwipeRefreshLayout mSwipeRefresh;
+
+    public SwipeRefreshLayoutTest() {
+        super(SwipeRefreshLayoutActivity.class);
+    }
+
+    @Before
+    public void setUp() {
+        mSwipeRefresh = (SwipeRefreshLayout) mActivityTestRule.getActivity().findViewById(
+                R.id.swipe_refresh);
+    }
+
+    @Test
+    @MediumTest
+    public void testStartAndStopRefreshing() throws Throwable {
+        assertFalse(mSwipeRefresh.isRefreshing());
+        for (int i = 0; i < 5; i++) {
+            onView(withId(R.id.swipe_refresh)).perform(setRefreshing());
+            assertTrue(mSwipeRefresh.isRefreshing());
+
+            // onView(..).perform(..) does not work when views are animated.
+            // Therefore this is using a posted task to turn off refreshing.
+            mSwipeRefresh.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    mSwipeRefresh.setRefreshing(false);
+                }
+            });
+            long waitTime = 1000;
+            while (mSwipeRefresh.isRefreshing()) {
+                Thread.sleep(20);
+                waitTime -= 20;
+                assertTrue("Timed out while waiting for SwipeRefreshLayout to stop refreshing",
+                        waitTime > 0);
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSize() throws Throwable {
+        float density = mSwipeRefresh.getResources().getDisplayMetrics().density;
+        assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER * density),
+                mSwipeRefresh.getProgressCircleDiameter());
+        onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.LARGE));
+        assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER_LARGE * density),
+                mSwipeRefresh.getProgressCircleDiameter());
+        onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.DEFAULT));
+        assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER * density),
+                mSwipeRefresh.getProgressCircleDiameter());
+        onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.DEFAULT));
+    }
+}
diff --git a/v4/tests/res/anim/long_fade_in.xml b/v4/tests/res/anim/long_fade_in.xml
new file mode 100644
index 0000000..5d6f496
--- /dev/null
+++ b/v4/tests/res/anim/long_fade_in.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>

+

+<alpha xmlns:android="http://schemas.android.com/apk/res/android"

+       android:interpolator="@android:interpolator/decelerate_quad"

+       android:fromAlpha="0.0" android:toAlpha="1.0"

+       android:duration="5000" />

diff --git a/v4/tests/res/anim/long_fade_out.xml b/v4/tests/res/anim/long_fade_out.xml
new file mode 100644
index 0000000..fe9fc6a
--- /dev/null
+++ b/v4/tests/res/anim/long_fade_out.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>

+

+<alpha xmlns:android="http://schemas.android.com/apk/res/android"

+       android:interpolator="@android:interpolator/decelerate_quad"

+       android:fromAlpha="1.0" android:toAlpha="0.0"

+       android:duration="5000" />

diff --git a/v4/tests/res/layout/strict_view_fragment.xml b/v4/tests/res/layout/strict_view_fragment.xml
new file mode 100644
index 0000000..324f8d0
--- /dev/null
+++ b/v4/tests/res/layout/strict_view_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/text1" />
diff --git a/v4/tests/res/layout/swipe_refresh_layout_activity.xml b/v4/tests/res/layout/swipe_refresh_layout_activity.xml
new file mode 100644
index 0000000..d925ecf
--- /dev/null
+++ b/v4/tests/res/layout/swipe_refresh_layout_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<android.support.v4.widget.SwipeRefreshLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/swipe_refresh"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+<!-- some full screen pullable view that will be the offsetable content -->
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/content"/>
+</android.support.v4.widget.SwipeRefreshLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w480dp/bools.xml b/v4/tests/res/transition/change_bounds.xml
similarity index 80%
rename from v7/appcompat/res/values-w480dp/bools.xml
rename to v4/tests/res/transition/change_bounds.xml
index 470f89b..766bcea 100644
--- a/v7/appcompat/res/values-w480dp/bools.xml
+++ b/v4/tests/res/transition/change_bounds.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,4 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
+<changeBounds/>
diff --git a/v7/appcompat/res/values-w480dp/bools.xml b/v4/tests/res/transition/fade.xml
similarity index 80%
copy from v7/appcompat/res/values-w480dp/bools.xml
copy to v4/tests/res/transition/fade.xml
index 470f89b..617f70e 100644
--- a/v7/appcompat/res/values-w480dp/bools.xml
+++ b/v4/tests/res/transition/fade.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,4 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
+<fade xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index 660223a..6816f54 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -26,7 +26,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-vectordrawable \
         android-support-animatedvectordrawable
 LOCAL_JAVA_LIBRARIES := android-support-v4
-LOCAL_AAPT_FLAGS += --auto-add-overlay
+LOCAL_AAPT_FLAGS += --auto-add-overlay \
+        --no-version-vectors
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index 9d7f1ab..3935c24 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -379,6 +379,7 @@
     field public static int alertDialogStyle;
     field public static int alertDialogTheme;
     field public static int allowStacking;
+    field public static int alpha;
     field public static int arrowHeadLength;
     field public static int arrowShaftLength;
     field public static int autoCompleteTextViewStyle;
@@ -394,6 +395,7 @@
     field public static int buttonBarNeutralButtonStyle;
     field public static int buttonBarPositiveButtonStyle;
     field public static int buttonBarStyle;
+    field public static int buttonGravity;
     field public static int buttonPanelSideLayout;
     field public static int buttonStyle;
     field public static int buttonStyleSmall;
@@ -407,6 +409,7 @@
     field public static int collapseIcon;
     field public static int color;
     field public static int colorAccent;
+    field public static int colorBackgroundFloating;
     field public static int colorButtonNormal;
     field public static int colorControlActivated;
     field public static int colorControlHighlight;
@@ -456,6 +459,7 @@
     field public static int listDividerAlertDialog;
     field public static int listItemLayout;
     field public static int listLayout;
+    field public static int listMenuViewStyle;
     field public static int listPopupWindowStyle;
     field public static int listPreferredItemHeight;
     field public static int listPreferredItemHeightLarge;
@@ -504,6 +508,7 @@
     field public static int splitTrack;
     field public static int srcCompat;
     field public static int state_above_anchor;
+    field public static int subMenuArrow;
     field public static int submitBackground;
     field public static int subtitle;
     field public static int subtitleTextAppearance;
@@ -518,6 +523,7 @@
     field public static int textAppearanceLargePopupMenu;
     field public static int textAppearanceListItem;
     field public static int textAppearanceListItemSmall;
+    field public static int textAppearancePopupMenuHeader;
     field public static int textAppearanceSearchResultSubtitle;
     field public static int textAppearanceSearchResultTitle;
     field public static int textAppearanceSmallPopupMenu;
@@ -526,12 +532,16 @@
     field public static int theme;
     field public static int thickness;
     field public static int thumbTextPadding;
+    field public static int tickMark;
+    field public static int tickMarkTint;
+    field public static int tickMarkTintMode;
     field public static int title;
+    field public static int titleMargin;
     field public static int titleMarginBottom;
     field public static int titleMarginEnd;
     field public static int titleMarginStart;
     field public static int titleMarginTop;
-    field public static int titleMargins;
+    field public static deprecated int titleMargins;
     field public static int titleTextAppearance;
     field public static int titleTextColor;
     field public static int titleTextStyle;
@@ -554,12 +564,10 @@
   public static final class R.bool {
     ctor public R.bool();
     field public static int abc_action_bar_embed_tabs;
-    field public static int abc_action_bar_embed_tabs_pre_jb;
-    field public static int abc_action_bar_expanded_action_views_exclusive;
     field public static int abc_allow_stacked_button_bar;
     field public static int abc_config_actionMenuItemAllCaps;
-    field public static int abc_config_allowActionMenuItemTextWithIcon;
     field public static int abc_config_closeDialogWhenTouchOutside;
+    field public static int abc_config_enableCascadingSubmenus;
     field public static int abc_config_showMenuShortcutsWhenKeyboardPresent;
   }
 
@@ -567,6 +575,7 @@
     ctor public R.color();
     field public static int abc_background_cache_hint_selector_material_dark;
     field public static int abc_background_cache_hint_selector_material_light;
+    field public static int abc_btn_colored_borderless_text_material;
     field public static int abc_color_highlight_material;
     field public static int abc_input_method_navigation_guard;
     field public static int abc_primary_text_disable_only_material_dark;
@@ -579,6 +588,13 @@
     field public static int abc_search_url_text_selected;
     field public static int abc_secondary_text_material_dark;
     field public static int abc_secondary_text_material_light;
+    field public static int abc_tint_btn_checkable;
+    field public static int abc_tint_default;
+    field public static int abc_tint_edittext;
+    field public static int abc_tint_seek_thumb;
+    field public static int abc_tint_spinner;
+    field public static int abc_tint_switch_thumb;
+    field public static int abc_tint_switch_track;
     field public static int accent_material_dark;
     field public static int accent_material_light;
     field public static int background_floating_material_dark;
@@ -683,8 +699,8 @@
     field public static int abc_floating_window_z;
     field public static int abc_list_item_padding_horizontal_material;
     field public static int abc_panel_menu_list_width;
+    field public static int abc_progress_bar_height_material;
     field public static int abc_search_view_preferred_width;
-    field public static int abc_search_view_text_min_width;
     field public static int abc_seekbar_track_background_height_material;
     field public static int abc_seekbar_track_progress_height_material;
     field public static int abc_select_dialog_padding_start_material;
@@ -700,6 +716,7 @@
     field public static int abc_text_size_headline_material;
     field public static int abc_text_size_large_material;
     field public static int abc_text_size_medium_material;
+    field public static int abc_text_size_menu_header_material;
     field public static int abc_text_size_menu_material;
     field public static int abc_text_size_small_material;
     field public static int abc_text_size_subhead_material;
@@ -722,40 +739,40 @@
     field public static int abc_action_bar_item_background_material;
     field public static int abc_btn_borderless_material;
     field public static int abc_btn_check_material;
-    field public static int abc_btn_check_to_on_mtrl_000;
-    field public static int abc_btn_check_to_on_mtrl_015;
+    field public static int abc_btn_checkbox_checked_mtrl;
+    field public static int abc_btn_checkbox_unchecked_mtrl;
     field public static int abc_btn_colored_material;
     field public static int abc_btn_default_mtrl_shape;
     field public static int abc_btn_radio_material;
-    field public static int abc_btn_radio_to_on_mtrl_000;
-    field public static int abc_btn_radio_to_on_mtrl_015;
-    field public static int abc_btn_rating_star_off_mtrl_alpha;
-    field public static int abc_btn_rating_star_on_mtrl_alpha;
+    field public static int abc_btn_radio_off_mtrl;
+    field public static int abc_btn_radio_on_mtrl;
     field public static int abc_btn_switch_to_on_mtrl_00001;
     field public static int abc_btn_switch_to_on_mtrl_00012;
     field public static int abc_cab_background_internal_bg;
     field public static int abc_cab_background_top_material;
     field public static int abc_cab_background_top_mtrl_alpha;
     field public static int abc_control_background_material;
-    field public static int abc_dialog_material_background_dark;
-    field public static int abc_dialog_material_background_light;
+    field public static int abc_dialog_material_background;
     field public static int abc_edit_text_material;
-    field public static int abc_ic_ab_back_mtrl_am_alpha;
-    field public static int abc_ic_clear_mtrl_alpha;
+    field public static int abc_ic_ab_back_material;
+    field public static int abc_ic_arrow_drop_right_black_24dp;
+    field public static int abc_ic_clear_material;
     field public static int abc_ic_commit_search_api_mtrl_alpha;
-    field public static int abc_ic_go_search_api_mtrl_alpha;
-    field public static int abc_ic_menu_copy_mtrl_am_alpha;
-    field public static int abc_ic_menu_cut_mtrl_alpha;
-    field public static int abc_ic_menu_moreoverflow_mtrl_alpha;
-    field public static int abc_ic_menu_paste_mtrl_am_alpha;
-    field public static int abc_ic_menu_selectall_mtrl_alpha;
-    field public static int abc_ic_menu_share_mtrl_alpha;
-    field public static int abc_ic_search_api_mtrl_alpha;
+    field public static int abc_ic_go_search_api_material;
+    field public static int abc_ic_menu_copy_material;
+    field public static int abc_ic_menu_cut_material;
+    field public static int abc_ic_menu_overflow_material;
+    field public static int abc_ic_menu_paste_material;
+    field public static int abc_ic_menu_selectall_material;
+    field public static int abc_ic_menu_share_material;
+    field public static int abc_ic_search_api_material;
     field public static int abc_ic_star_black_16dp;
     field public static int abc_ic_star_black_36dp;
+    field public static int abc_ic_star_black_48dp;
     field public static int abc_ic_star_half_black_16dp;
     field public static int abc_ic_star_half_black_36dp;
-    field public static int abc_ic_voice_search_api_mtrl_alpha;
+    field public static int abc_ic_star_half_black_48dp;
+    field public static int abc_ic_voice_search_api_material;
     field public static int abc_item_background_holo_dark;
     field public static int abc_item_background_holo_light;
     field public static int abc_list_divider_mtrl_alpha;
@@ -771,8 +788,8 @@
     field public static int abc_list_selector_holo_light;
     field public static int abc_menu_hardkey_panel_mtrl_mult;
     field public static int abc_popup_background_mtrl_mult;
-    field public static int abc_ratingbar_full_material;
     field public static int abc_ratingbar_indicator_material;
+    field public static int abc_ratingbar_material;
     field public static int abc_ratingbar_small_material;
     field public static int abc_scrubber_control_off_mtrl_alpha;
     field public static int abc_scrubber_control_to_pressed_mtrl_000;
@@ -780,6 +797,7 @@
     field public static int abc_scrubber_primary_mtrl_alpha;
     field public static int abc_scrubber_track_mtrl_alpha;
     field public static int abc_seekbar_thumb_material;
+    field public static int abc_seekbar_tick_mark_material;
     field public static int abc_seekbar_track_material;
     field public static int abc_spinner_mtrl_am_alpha;
     field public static int abc_spinner_textfield_background_material;
@@ -814,9 +832,11 @@
     field public static int action_mode_bar_stub;
     field public static int action_mode_close_button;
     field public static int activity_chooser_view_content;
+    field public static int add;
     field public static int alertTitle;
     field public static int always;
     field public static int beginning;
+    field public static int bottom;
     field public static int buttonPanel;
     field public static int cancel_action;
     field public static int checkbox;
@@ -878,6 +898,7 @@
     field public static int src_in;
     field public static int src_over;
     field public static int status_bar_latest_event_content;
+    field public static int submenuarrow;
     field public static int submit_area;
     field public static int tabMode;
     field public static int text;
@@ -886,6 +907,7 @@
     field public static int time;
     field public static int title;
     field public static int title_template;
+    field public static int top;
     field public static int topPanel;
     field public static int up;
     field public static int useLogo;
@@ -897,7 +919,6 @@
     ctor public R.integer();
     field public static int abc_config_activityDefaultDur;
     field public static int abc_config_activityShortDur;
-    field public static int abc_max_action_buttons;
     field public static int cancel_button_image_alpha;
     field public static int status_bar_notification_info_maxnum;
   }
@@ -921,6 +942,7 @@
     field public static int abc_list_menu_item_icon;
     field public static int abc_list_menu_item_layout;
     field public static int abc_list_menu_item_radio;
+    field public static int abc_popup_menu_header_item_layout;
     field public static int abc_popup_menu_item_layout;
     field public static int abc_screen_content_include;
     field public static int abc_screen_simple;
@@ -955,6 +977,18 @@
     field public static int abc_activitychooserview_choose_application;
     field public static int abc_capital_off;
     field public static int abc_capital_on;
+    field public static int abc_font_family_body_1_material;
+    field public static int abc_font_family_body_2_material;
+    field public static int abc_font_family_button_material;
+    field public static int abc_font_family_caption_material;
+    field public static int abc_font_family_display_1_material;
+    field public static int abc_font_family_display_2_material;
+    field public static int abc_font_family_display_3_material;
+    field public static int abc_font_family_display_4_material;
+    field public static int abc_font_family_headline_material;
+    field public static int abc_font_family_menu_material;
+    field public static int abc_font_family_subhead_material;
+    field public static int abc_font_family_title_material;
     field public static int abc_search_hint;
     field public static int abc_searchview_description_clear;
     field public static int abc_searchview_description_query;
@@ -1016,6 +1050,7 @@
     field public static int Base_TextAppearance_AppCompat_Widget_Button;
     field public static int Base_TextAppearance_AppCompat_Widget_Button_Inverse;
     field public static int Base_TextAppearance_AppCompat_Widget_DropDownItem;
+    field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header;
     field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large;
     field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small;
     field public static int Base_TextAppearance_AppCompat_Widget_Switch;
@@ -1027,6 +1062,8 @@
     field public static int Base_ThemeOverlay_AppCompat_ActionBar;
     field public static int Base_ThemeOverlay_AppCompat_Dark;
     field public static int Base_ThemeOverlay_AppCompat_Dark_ActionBar;
+    field public static int Base_ThemeOverlay_AppCompat_Dialog;
+    field public static int Base_ThemeOverlay_AppCompat_Dialog_Alert;
     field public static int Base_ThemeOverlay_AppCompat_Light;
     field public static int Base_Theme_AppCompat;
     field public static int Base_Theme_AppCompat_CompactMenu;
@@ -1042,10 +1079,12 @@
     field public static int Base_Theme_AppCompat_Light_Dialog_Alert;
     field public static int Base_Theme_AppCompat_Light_Dialog_FixedSize;
     field public static int Base_Theme_AppCompat_Light_Dialog_MinWidth;
+    field public static int Base_V11_ThemeOverlay_AppCompat_Dialog;
     field public static int Base_V11_Theme_AppCompat_Dialog;
     field public static int Base_V11_Theme_AppCompat_Light_Dialog;
     field public static int Base_V12_Widget_AppCompat_AutoCompleteTextView;
     field public static int Base_V12_Widget_AppCompat_EditText;
+    field public static int Base_V21_ThemeOverlay_AppCompat_Dialog;
     field public static int Base_V21_Theme_AppCompat;
     field public static int Base_V21_Theme_AppCompat_Dialog;
     field public static int Base_V21_Theme_AppCompat_Light;
@@ -1054,6 +1093,7 @@
     field public static int Base_V22_Theme_AppCompat_Light;
     field public static int Base_V23_Theme_AppCompat;
     field public static int Base_V23_Theme_AppCompat_Light;
+    field public static int Base_V7_ThemeOverlay_AppCompat_Dialog;
     field public static int Base_V7_Theme_AppCompat;
     field public static int Base_V7_Theme_AppCompat_Dialog;
     field public static int Base_V7_Theme_AppCompat_Light;
@@ -1095,6 +1135,7 @@
     field public static int Base_Widget_AppCompat_Light_ActionBar_TabView;
     field public static int Base_Widget_AppCompat_Light_PopupMenu;
     field public static int Base_Widget_AppCompat_Light_PopupMenu_Overflow;
+    field public static int Base_Widget_AppCompat_ListMenuView;
     field public static int Base_Widget_AppCompat_ListPopupWindow;
     field public static int Base_Widget_AppCompat_ListView;
     field public static int Base_Widget_AppCompat_ListView_DropDown;
@@ -1110,6 +1151,7 @@
     field public static int Base_Widget_AppCompat_SearchView;
     field public static int Base_Widget_AppCompat_SearchView_ActionBar;
     field public static int Base_Widget_AppCompat_SeekBar;
+    field public static int Base_Widget_AppCompat_SeekBar_Discrete;
     field public static int Base_Widget_AppCompat_Spinner;
     field public static int Base_Widget_AppCompat_Spinner_Underlined;
     field public static int Base_Widget_AppCompat_TextView_SpinnerItem;
@@ -1179,6 +1221,7 @@
     field public static int TextAppearance_AppCompat_Widget_Button;
     field public static int TextAppearance_AppCompat_Widget_Button_Inverse;
     field public static int TextAppearance_AppCompat_Widget_DropDownItem;
+    field public static int TextAppearance_AppCompat_Widget_PopupMenu_Header;
     field public static int TextAppearance_AppCompat_Widget_PopupMenu_Large;
     field public static int TextAppearance_AppCompat_Widget_PopupMenu_Small;
     field public static int TextAppearance_AppCompat_Widget_Switch;
@@ -1195,6 +1238,8 @@
     field public static int ThemeOverlay_AppCompat_ActionBar;
     field public static int ThemeOverlay_AppCompat_Dark;
     field public static int ThemeOverlay_AppCompat_Dark_ActionBar;
+    field public static int ThemeOverlay_AppCompat_Dialog;
+    field public static int ThemeOverlay_AppCompat_Dialog_Alert;
     field public static int ThemeOverlay_AppCompat_Light;
     field public static int Theme_AppCompat;
     field public static int Theme_AppCompat_CompactMenu;
@@ -1265,6 +1310,7 @@
     field public static int Widget_AppCompat_Light_PopupMenu_Overflow;
     field public static int Widget_AppCompat_Light_SearchView;
     field public static int Widget_AppCompat_Light_Spinner_DropDown_ActionBar;
+    field public static int Widget_AppCompat_ListMenuView;
     field public static int Widget_AppCompat_ListPopupWindow;
     field public static int Widget_AppCompat_ListView;
     field public static int Widget_AppCompat_ListView_DropDown;
@@ -1280,6 +1326,7 @@
     field public static int Widget_AppCompat_SearchView;
     field public static int Widget_AppCompat_SearchView_ActionBar;
     field public static int Widget_AppCompat_SeekBar;
+    field public static int Widget_AppCompat_SeekBar_Discrete;
     field public static int Widget_AppCompat_Spinner;
     field public static int Widget_AppCompat_Spinner_DropDown;
     field public static int Widget_AppCompat_Spinner_DropDown_ActionBar;
@@ -1344,6 +1391,11 @@
     field public static final int[] AppCompatImageView;
     field public static int AppCompatImageView_android_src;
     field public static int AppCompatImageView_srcCompat;
+    field public static final int[] AppCompatSeekBar;
+    field public static int AppCompatSeekBar_android_thumb;
+    field public static int AppCompatSeekBar_tickMark;
+    field public static int AppCompatSeekBar_tickMarkTint;
+    field public static int AppCompatSeekBar_tickMarkTintMode;
     field public static final int[] AppCompatTextView;
     field public static int AppCompatTextView_android_textAppearance;
     field public static int AppCompatTextView_textAllCaps;
@@ -1397,6 +1449,7 @@
     field public static int AppCompatTheme_checkboxStyle;
     field public static int AppCompatTheme_checkedTextViewStyle;
     field public static int AppCompatTheme_colorAccent;
+    field public static int AppCompatTheme_colorBackgroundFloating;
     field public static int AppCompatTheme_colorButtonNormal;
     field public static int AppCompatTheme_colorControlActivated;
     field public static int AppCompatTheme_colorControlHighlight;
@@ -1418,6 +1471,7 @@
     field public static int AppCompatTheme_imageButtonStyle;
     field public static int AppCompatTheme_listChoiceBackgroundIndicator;
     field public static int AppCompatTheme_listDividerAlertDialog;
+    field public static int AppCompatTheme_listMenuViewStyle;
     field public static int AppCompatTheme_listPopupWindowStyle;
     field public static int AppCompatTheme_listPreferredItemHeight;
     field public static int AppCompatTheme_listPreferredItemHeightLarge;
@@ -1443,6 +1497,7 @@
     field public static int AppCompatTheme_textAppearanceLargePopupMenu;
     field public static int AppCompatTheme_textAppearanceListItem;
     field public static int AppCompatTheme_textAppearanceListItemSmall;
+    field public static int AppCompatTheme_textAppearancePopupMenuHeader;
     field public static int AppCompatTheme_textAppearanceSearchResultSubtitle;
     field public static int AppCompatTheme_textAppearanceSearchResultTitle;
     field public static int AppCompatTheme_textAppearanceSmallPopupMenu;
@@ -1461,6 +1516,10 @@
     field public static int AppCompatTheme_windowMinWidthMinor;
     field public static int AppCompatTheme_windowNoTitle;
     field public static int ButtonBarLayout_allowStacking;
+    field public static final int[] ColorStateListItem;
+    field public static int ColorStateListItem_alpha;
+    field public static int ColorStateListItem_android_alpha;
+    field public static int ColorStateListItem_android_color;
     field public static final int[] CompoundButton;
     field public static int CompoundButton_android_button;
     field public static int CompoundButton_buttonTint;
@@ -1526,6 +1585,7 @@
     field public static int MenuView_android_verticalDivider;
     field public static int MenuView_android_windowAnimationStyle;
     field public static int MenuView_preserveIconSpacing;
+    field public static int MenuView_subMenuArrow;
     field public static final int[] PopupWindow;
     field public static final int[] PopupWindowBackgroundState;
     field public static int PopupWindowBackgroundState_state_above_anchor;
@@ -1576,9 +1636,10 @@
     field public static int TextAppearance_android_textStyle;
     field public static int TextAppearance_android_typeface;
     field public static int TextAppearance_textAllCaps;
-    field public static final int[] Toolbar;
+    field public static final deprecated int[] Toolbar;
     field public static int Toolbar_android_gravity;
     field public static int Toolbar_android_minHeight;
+    field public static int Toolbar_buttonGravity;
     field public static int Toolbar_collapseContentDescription;
     field public static int Toolbar_collapseIcon;
     field public static int Toolbar_contentInsetEnd;
@@ -1595,11 +1656,12 @@
     field public static int Toolbar_subtitleTextAppearance;
     field public static int Toolbar_subtitleTextColor;
     field public static int Toolbar_title;
+    field public static int Toolbar_titleMargin;
     field public static int Toolbar_titleMarginBottom;
     field public static int Toolbar_titleMarginEnd;
     field public static int Toolbar_titleMarginStart;
     field public static int Toolbar_titleMarginTop;
-    field public static int Toolbar_titleMargins;
+    field public static deprecated int Toolbar_titleMargins;
     field public static int Toolbar_titleTextAppearance;
     field public static int Toolbar_titleTextColor;
     field public static final int[] View;
@@ -2061,6 +2123,10 @@
     method public int getPopupTheme();
     method public java.lang.CharSequence getSubtitle();
     method public java.lang.CharSequence getTitle();
+    method public int getTitleMarginBottom();
+    method public int getTitleMarginEnd();
+    method public int getTitleMarginStart();
+    method public int getTitleMarginTop();
     method public boolean hasExpandedActionView();
     method public boolean hideOverflowMenu();
     method public void inflateMenu(int);
@@ -2086,6 +2152,11 @@
     method public void setSubtitleTextColor(int);
     method public void setTitle(int);
     method public void setTitle(java.lang.CharSequence);
+    method public void setTitleMargin(int, int, int, int);
+    method public void setTitleMarginBottom(int);
+    method public void setTitleMarginEnd(int);
+    method public void setTitleMarginStart(int);
+    method public void setTitleMarginTop(int);
     method public void setTitleTextAppearance(android.content.Context, int);
     method public void setTitleTextColor(int);
     method public boolean showOverflowMenu();
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index c95103d..faa8439 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -25,6 +25,8 @@
     defaultConfig {
         minSdkVersion 7
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+        // This disables the builds tools automatic vector -> PNG generation
+        generatedDensities = []
     }
 
     sourceSets {
@@ -45,6 +47,10 @@
         targetCompatibility JavaVersion.VERSION_1_7
     }
 
+    aaptOptions {
+        additionalParameters "--no-version-vectors"
+    }
+
     lintOptions {
         // TODO: fix errors and reenable.
         abortOnError false
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 91d8acf..c0b350c 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -50,6 +50,7 @@
      <public type="attr" name="actionViewClass"/>
      <public type="attr" name="alertDialogStyle"/>
      <public type="attr" name="alertDialogTheme"/>
+     <public type="attr" name="alpha"/>
      <public type="attr" name="arrowHeadLength"/>
      <public type="attr" name="arrowShaftLength"/>
      <public type="attr" name="autoCompleteTextViewStyle"/>
@@ -65,6 +66,7 @@
      <public type="attr" name="buttonBarNeutralButtonStyle"/>
      <public type="attr" name="buttonBarPositiveButtonStyle"/>
      <public type="attr" name="buttonBarStyle"/>
+     <public type="attr" name="buttonGravity" />
      <public type="attr" name="buttonStyle"/>
      <public type="attr" name="buttonStyleSmall"/>
      <public type="attr" name="buttonTint"/>
@@ -77,6 +79,7 @@
      <public type="attr" name="collapseIcon"/>
      <public type="attr" name="color"/>
      <public type="attr" name="colorAccent"/>
+     <public type="attr" name="colorBackgroundFloating"/>
      <public type="attr" name="colorButtonNormal"/>
      <public type="attr" name="colorControlActivated"/>
      <public type="attr" name="colorControlHighlight"/>
@@ -127,6 +130,7 @@
      <public type="attr" name="listPreferredItemPaddingRight"/>
      <public type="attr" name="logo"/>
      <public type="attr" name="logoDescription"/>
+     <public type="attr" name="maxButtonHeight" />
      <public type="attr" name="measureWithLargestChild"/>
      <public type="attr" name="navigationContentDescription"/>
      <public type="attr" name="navigationIcon"/>
@@ -175,6 +179,7 @@
      <public type="attr" name="textAppearanceLargePopupMenu"/>
      <public type="attr" name="textAppearanceListItem"/>
      <public type="attr" name="textAppearanceListItemSmall"/>
+     <public type="attr" name="textAppearancePopupMenuHeader"/>
      <public type="attr" name="textAppearanceSearchResultSubtitle"/>
      <public type="attr" name="textAppearanceSearchResultTitle"/>
      <public type="attr" name="textAppearanceSmallPopupMenu"/>
@@ -182,9 +187,13 @@
      <public type="attr" name="theme"/>
      <public type="attr" name="thickness"/>
      <public type="attr" name="thumbTextPadding"/>
+     <public type="attr" name="tickMark"/>
+     <public type="attr" name="tickMarkTint"/>
+     <public type="attr" name="tickMarkTintMode"/>
      <public type="attr" name="title"/>
      <public type="attr" name="titleMarginBottom"/>
      <public type="attr" name="titleMarginEnd"/>
+     <public type="attr" name="titleMargin"/>
      <public type="attr" name="titleMargins"/>
      <public type="attr" name="titleMarginStart"/>
      <public type="attr" name="titleMarginTop"/>
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index aba9c8b..9e6df30 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -56,6 +56,7 @@
     <public type="style" name="TextAppearance.AppCompat.Widget.Button"/>
     <public type="style" name="TextAppearance.AppCompat.Widget.Button.Inverse"/>
     <public type="style" name="TextAppearance.AppCompat.Widget.DropDownItem"/>
+    <public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Header"/>
     <public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Large"/>
     <public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Small"/>
     <public type="style" name="TextAppearance.AppCompat.Widget.Switch"/>
@@ -84,6 +85,8 @@
     <public type="style" name="ThemeOverlay.AppCompat.ActionBar"/>
     <public type="style" name="ThemeOverlay.AppCompat.Dark"/>
     <public type="style" name="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+    <public type="style" name="ThemeOverlay.AppCompat.Dialog"/>
+    <public type="style" name="ThemeOverlay.AppCompat.Dialog.Alert"/>
     <public type="style" name="ThemeOverlay.AppCompat.Light"/>
     <public type="style" name="Widget.AppCompat.ActionBar"/>
     <public type="style" name="Widget.AppCompat.ActionBar.Solid"/>
@@ -146,6 +149,7 @@
     <public type="style" name="Widget.AppCompat.SearchView"/>
     <public type="style" name="Widget.AppCompat.SearchView.ActionBar"/>
     <public type="style" name="Widget.AppCompat.SeekBar"/>
+    <public type="style" name="Widget.AppCompat.SeekBar.Discrete"/>
     <public type="style" name="Widget.AppCompat.Spinner"/>
     <public type="style" name="Widget.AppCompat.Spinner.DropDown"/>
     <public type="style" name="Widget.AppCompat.Spinner.DropDown.ActionBar"/>
diff --git a/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
new file mode 100644
index 0000000..1480046
--- /dev/null
+++ b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Used for the text of a borderless colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false"
+          app:alpha="?android:attr/disabledAlpha"
+          android:color="?android:attr/textColorSecondary"/>
+    <item android:color="?attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_btn_checkable.xml b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
new file mode 100644
index 0000000..0c663f6
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+    <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+    <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_default.xml b/v7/appcompat/res/color/abc_tint_default.xml
new file mode 100644
index 0000000..8d7c391
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_default.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+    <item android:state_focused="true" android:color="?attr/colorControlActivated"/>
+    <item android:state_pressed="true" android:color="?attr/colorControlActivated"/>
+    <item android:state_activated="true" android:color="?attr/colorControlActivated"/>
+    <item android:state_selected="true" android:color="?attr/colorControlActivated"/>
+    <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+    <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_edittext.xml b/v7/appcompat/res/color/abc_tint_edittext.xml
new file mode 100644
index 0000000..536d77f
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_edittext.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorControlNormal"
+          app:alpha="?android:disabledAlpha"/>
+    <item android:state_pressed="false" android:state_focused="false"
+          android:color="?attr/colorControlNormal"/>
+    <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
similarity index 61%
copy from v7/appcompat/res/values-land/config.xml
copy to v7/appcompat/res/color/abc_tint_seek_thumb.xml
index d0d990d..cb53788 100644
--- a/v7/appcompat/res/values-land/config.xml
+++ b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!--
+     Copyright (C) 2015 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -13,6 +14,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorControlActivated" app:alpha="?android:attr/disabledAlpha"/>
+    <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_spinner.xml b/v7/appcompat/res/color/abc_tint_spinner.xml
new file mode 100644
index 0000000..44333dd
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_spinner.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+    <item android:state_pressed="false" android:state_focused="false" android:color="?attr/colorControlNormal"/>
+    <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_thumb.xml b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
new file mode 100644
index 0000000..fc8bd24
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?attr/colorSwitchThumbNormal" app:alpha="?android:attr/disabledAlpha"/>
+    <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+    <item android:color="?attr/colorSwitchThumbNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_track.xml b/v7/appcompat/res/color/abc_tint_switch_track.xml
new file mode 100644
index 0000000..22322f8
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_track.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:state_enabled="false" android:color="?android:attr/colorForeground" app:alpha="0.1"/>
+    <item android:state_checked="true" android:color="?attr/colorControlActivated" app:alpha="0.3"/>
+    <item android:color="?android:attr/colorForeground" app:alpha="0.3"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 9911008..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 69ff9dd..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 9218981..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index a588576..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
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
deleted file mode 100644
index 1b12adf..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 30c8c99..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 46ccacc..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index ce64334..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index f39c153..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 706fc1f..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index e78bcaf..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index 00e189b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 8610c50..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index e631df7..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index cd1f57c..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index cc6d5da..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..54d3065
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..5f0f27d
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index a6dbc2a..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 57211b5..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index a262b0c..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index d8eaf07..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index b872414..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 254f806..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index efe4446..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index ffa1654..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 88e34c4..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 87bf8d3..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 33b4f0f..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index fb54215..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 3cdb6cf..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index a91425a..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index fb91811..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 90fe333..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 7a9fcbc..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 3b052e5..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 96a8693..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 6e18d40..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
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
deleted file mode 100644
index 27a75a7..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 14e9e09..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 90fbc56..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index dde307e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index 0084c12..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 559b835..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 1c0a1e9..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index 234a958..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 1492ab6..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 7c011af..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 36f664c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index faefc59..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..c636ce8
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..28a1723
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index a216da1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 4902520..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 59a683a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 03bf49c..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 342323b..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
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
deleted file mode 100644
index 05e6afe..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 7116604..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 4feb8a3..1d29f9a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 2bf9208..92b43ba 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 154babd..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index 6d90d0c..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index 19e000a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 6448549..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index cd38901..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index e76c83e..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 9aabc43..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 6a7161f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 6be7e09..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 4510e52..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..7be2280
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..c401533
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index 7329c15..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index accf80e..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 8c82ec3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 8fc0a9b..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 3038d70..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
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
deleted file mode 100644
index 37b8583..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index 4a3bd14..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index f99802f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index b85e87f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index d041623..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 90d6ba3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 63e541f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index c382aa6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index f71485c..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 162ab98..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index d95a377..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 0ada3a3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..918a395
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..25c5bf2
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index dacf407..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index d44bbae..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 4e18de2..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 5fa3266..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index c11cb2e..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 41d6d6b..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index da2b577..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 715db8a..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 397fd91..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index bb074a6..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 1891b3d..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 591a1c9..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
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
deleted file mode 100644
index ba16aac..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 5430b2c..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..67e25d5
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..d714ad6
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index d191642..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_btn_check_material.xml b/v7/appcompat/res/drawable/abc_btn_check_material.xml
index f6e938f..f8e90a9 100644
--- a/v7/appcompat/res/drawable/abc_btn_check_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_check_material.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:drawable="@drawable/abc_btn_check_to_on_mtrl_015" />
-    <item android:drawable="@drawable/abc_btn_check_to_on_mtrl_000" />
-</selector>
+    <item android:state_checked="true" android:drawable="@drawable/abc_btn_checkbox_checked_mtrl" />
+    <item android:drawable="@drawable/abc_btn_checkbox_unchecked_mtrl" />
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml
new file mode 100644
index 0000000..638339a
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="abc_btn_checkbox_checked"
+        android:width="32dp"
+        android:viewportWidth="48"
+        android:height="32dp"
+        android:viewportHeight="48">
+    <group
+            android:name="icon_null"
+            android:translateX="24"
+            android:translateY="24"
+            android:scaleX="0.2"
+            android:scaleY="0.2">
+        <group
+                android:name="check"
+                android:scaleX="7.5"
+                android:scaleY="7.5">
+            <path
+                    android:name="check_path_merged"
+                    android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -5.0,-5.00001525879 -5.0,-5.00001525879 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 3.58590698242,3.58601379395 3.58590698242,3.58601379395 c 0.0,0.0 7.58590698242,-7.58601379395 7.58590698242,-7.58601379395 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -9.0,9.00001525879 -9.0,9.00001525879 Z"
+                    android:fillColor="#FF000000"/>
+        </group>
+        <group
+                android:name="box_dilate"
+                android:scaleX="7.5"
+                android:scaleY="7.5">
+            <path
+                    android:name="box_inner_merged"
+                    android:pathData="M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+                    android:fillColor="#FF000000"
+                    android:fillAlpha="0"/>
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml
new file mode 100644
index 0000000..0301f1f
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="abc_btn_checkbox_unchecked"
+        android:width="32dp"
+        android:viewportWidth="48"
+        android:height="32dp"
+        android:viewportHeight="48">
+    <group
+            android:name="icon_null"
+            android:translateX="24"
+            android:translateY="24"
+            android:scaleX="0.2"
+            android:scaleY="0.2">
+        <group
+                android:name="check"
+                android:scaleX="7.5"
+                android:scaleY="7.5">
+            <path
+                    android:name="box_outer_merged"
+                    android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -1.4234161377,-1.40159606934 -1.4234161377,-1.40159606934 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 0.00932312011719,-0.0124053955078 0.00932312011719,-0.0124053955078 c 0.0,0.0 0.0234069824219,-0.0235137939453 0.0234069824219,-0.0235137939453 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -1.4375,1.43751525879 -1.4375,1.43751525879 Z"
+                    android:fillColor="#FF000000"
+                    android:fillAlpha="0"/>
+        </group>
+        <group
+                android:name="box_dilate"
+                android:scaleX="7.5"
+                android:scaleY="7.5">
+            <path
+                    android:name="box_inner_merged"
+                    android:pathData="M -7.0,-7.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+                    android:fillColor="#FF000000"/>
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_material.xml b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
index 6e9f9cf..ac4eead 100644
--- a/v7/appcompat/res/drawable/abc_btn_radio_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
@@ -15,6 +15,6 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_to_on_mtrl_015" />
-    <item android:drawable="@drawable/abc_btn_radio_to_on_mtrl_000" />
+    <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_on_mtrl" />
+    <item android:drawable="@drawable/abc_btn_radio_off_mtrl" />
 </selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml
new file mode 100644
index 0000000..9c4f3b1
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="btn_radio_to_on_mtrl"
+        android:width="32dp"
+        android:viewportWidth="32"
+        android:height="32dp"
+        android:viewportHeight="32">
+    <group
+            android:name="btn_radio_to_on_mtrl_0"
+            android:translateX="16"
+            android:translateY="16" >
+        <group
+                android:name="ring_outer" >
+            <path
+                    android:name="ring_outer_path"
+                    android:strokeColor="#FF000000"
+                    android:strokeWidth="2"
+                    android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+        </group>
+        <group
+                android:name="dot_group"
+                android:scaleX="0"
+                android:scaleY="0" >
+            <path
+                    android:name="dot_path"
+                    android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+                    android:fillColor="#FF000000" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml
new file mode 100644
index 0000000..ab56e58
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="btn_radio_to_off_mtrl"
+        android:width="32dp"
+        android:viewportWidth="32"
+        android:height="32dp"
+        android:viewportHeight="32">
+    <group
+            android:name="btn_radio_to_off_mtrl_0"
+            android:translateX="16"
+            android:translateY="16" >
+        <group
+                android:name="ring_outer" >
+            <path
+                    android:name="ring_outer_path"
+                    android:strokeColor="#FF000000"
+                    android:strokeWidth="2"
+                    android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+        </group>
+        <group
+                android:name="dot_group" >
+            <path
+                    android:name="dot_path"
+                    android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+                    android:fillColor="#FF000000" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
similarity index 87%
rename from v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
rename to v7/appcompat/res/drawable/abc_dialog_material_background.xml
index 248b13a..18560fc 100644
--- a/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
+++ b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- 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.
@@ -21,6 +21,6 @@
        android:insetBottom="16dp">
     <shape android:shape="rectangle">
         <corners android:radius="2dp" />
-        <solid android:color="@color/background_floating_material_light" />
+        <solid android:color="@android:color/white" />
     </shape>
 </inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml b/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
deleted file mode 100644
index 41c4a6f..0000000
--- a/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-       android:insetLeft="16dp"
-       android:insetTop="16dp"
-       android:insetRight="16dp"
-       android:insetBottom="16dp">
-    <shape android:shape="rectangle">
-        <corners android:radius="2dp" />
-        <solid android:color="@color/background_floating_material_dark" />
-    </shape>
-</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml b/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml
new file mode 100644
index 0000000..5a89523
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M20,11L7.8,11l5.6,-5.6L12,4l-8,8l8,8l1.4,-1.4L7.8,13L20,13L20,11z"
+            android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
new file mode 100644
index 0000000..68547eb
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="24dp"
+        android:viewportHeight="24.0"
+        android:viewportWidth="24.0"
+        android:width="24dp"
+        android:tint="?attr/colorControlNormal"
+        android:autoMirrored="true">
+
+    <group
+            android:name="arrow"
+            android:rotation="90.0"
+            android:pivotX="12.0"
+            android:pivotY="12.0">
+        <path android:fillColor="@android:color/black" android:pathData="M7,14 L12,9 L17,14 L7,14 Z" />
+        <path android:pathData="M0,0 L24,0 L24,24 L0,24 L0,0 Z" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_clear_material.xml b/v7/appcompat/res/drawable/abc_ic_clear_material.xml
new file mode 100644
index 0000000..e6d106b
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_clear_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M19,6.41L17.59,5,12,10.59,6.41,5,5,6.41,10.59,12,5,17.59,6.41,19,12,13.41,17.59,19,19,17.59,13.41,12z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml
new file mode 100644
index 0000000..0c88119
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:pathData="M10,6l-1.4,1.4 4.599999,4.6 -4.599999,4.6 1.4,1.4 6,-6z"
+        android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml
new file mode 100644
index 0000000..6af0e0c
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M16,1L4,1C2.9,1 2,1.9 2,3l0,14l2,0L4,3l12,0L16,1zM19,5L8,5C6.9,5 6,5.9 6,7l0,14c0,1.1 0.9,2 2,2l11,0c1.1,0 2,-0.9 2,-2L21,7C21,5.9 20.1,5 19,5zM19,21L8,21L8,7l11,0L19,21z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml
new file mode 100644
index 0000000..22cb81d
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M10,6c0,-2.2 -1.8,-4 -4,-4S2,3.8 2,6c0,2.2 1.8,4 4,4c0.6,0 1.1,-0.1 1.6,-0.4L10,12l-2.4,2.4C7.1,14.1 6.6,14 6,14c-2.2,0 -4,1.8 -4,4c0,2.2 1.8,4 4,4s4,-1.8 4,-4c0,-0.6 -0.1,-1.1 -0.4,-1.6L12,14l7,7l4,0L9.6,7.6C9.9,7.1 10,6.6 10,6zM6,8C4.9,8 4,7.1 4,6s0.9,-2 2,-2c1.1,0 2,0.9 2,2S7.1,8 6,8zM6,20c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2c1.1,0 2,0.9 2,2S7.1,20 6,20zM12,11.5c0.3,0 0.5,0.2 0.5,0.5c0,0.3 -0.2,0.5 -0.5,0.5c-0.3,0 -0.5,-0.2 -0.5,-0.5C11.5,11.7 11.7,11.5 12,11.5zM23,3l-4,0l-6,6l2,2L23,3z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml
new file mode 100644
index 0000000..1420edd
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2S10.9,8 12,8zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,10 12,10zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,16 12,16z"
+            android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml
new file mode 100644
index 0000000..b642aae
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M19,2l-4.2,0c-0.4,-1.2 -1.5,-2 -2.8,-2c-1.3,0 -2.4,0.8 -2.8,2L5,2C3.9,2 3,2.9 3,4l0,16c0,1.1 0.9,2 2,2l14,0c1.1,0 2,-0.9 2,-2L21,4C21,2.9 20.1,2 19,2zM12,2c0.6,0 1,0.4 1,1s-0.4,1 -1,1c-0.6,0 -1,-0.4 -1,-1S11.4,2 12,2zM19,20L5,20L5,4l2,0l0,3l10,0L17,4l2,0L19,20z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml
new file mode 100644
index 0000000..cd0bbd5
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M3,5l2,0L5,3C3.9,3 3,3.9 3,5zM3,13l2,0l0,-2L3,11L3,13zM7,21l2,0l0,-2L7,19L7,21zM3,9l2,0L5,7L3,7L3,9zM13,3l-2,0l0,2l2,0L13,3zM19,3l0,2l2,0C21,3.9 20.1,3 19,3zM5,21l0,-2L3,19C3,20.1 3.9,21 5,21zM3,17l2,0l0,-2L3,15L3,17zM9,3L7,3l0,2l2,0L9,3zM11,21l2,0l0,-2l-2,0L11,21zM19,13l2,0l0,-2l-2,0L19,13zM19,21c1.1,0 2,-0.9 2,-2l-2,0L19,21zM19,9l2,0L21,7l-2,0L19,9zM19,17l2,0l0,-2l-2,0L19,17zM15,21l2,0l0,-2l-2,0L15,21zM15,5l2,0L17,3l-2,0L15,5zM7,17l10,0L17,7L7,7L7,17zM9,9l6,0l0,6L9,15L9,9z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml
new file mode 100644
index 0000000..ce5ceff
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+            android:pathData="M18,16.1c-0.8,0 -1.5,0.3 -2,0.8l-7.1,-4.2C9,12.5 9,12.2 9,12s0,-0.5 -0.1,-0.7L16,7.2C16.5,7.7 17.200001,8 18,8c1.7,0 3,-1.3 3,-3s-1.3,-3 -3,-3s-3,1.3 -3,3c0,0.2 0,0.5 0.1,0.7L8,9.8C7.5,9.3 6.8,9 6,9c-1.7,0 -2.9,1.2 -2.9,2.9s1.3,3 3,3c0.8,0 1.5,-0.3 2,-0.8l7.1,4.2c-0.1,0.3 -0.1,0.5 -0.1,0.7c0,1.6 1.3,2.9 2.9,2.9s2.9,-1.3 2.9,-2.9S19.6,16.1 18,16.1z"
+            android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_search_api_material.xml
new file mode 100644
index 0000000..b4cba34
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_search_api_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:pathData="M15.5,14l-0.8,0l-0.3,-0.3c1,-1.1 1.6,-2.6 1.6,-4.2C16,5.9 13.1,3 9.5,3C5.9,3 3,5.9 3,9.5S5.9,16 9.5,16c1.6,0 3.1,-0.6 4.2,-1.6l0.3,0.3l0,0.8l5,5l1.5,-1.5L15.5,14zM9.5,14C7,14 5,12 5,9.5S7,5 9.5,5C12,5 14,7 14,9.5S12,14 9.5,14z"
+        android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml
new file mode 100644
index 0000000..143db55
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:pathData="M12,14c1.7,0 3,-1.3 3,-3l0,-6c0,-1.7 -1.3,-3 -3,-3c-1.7,0 -3,1.3 -3,3l0,6C9,12.7 10.3,14 12,14zM17.299999,11c0,3 -2.5,5.1 -5.3,5.1c-2.8,0 -5.3,-2.1 -5.3,-5.1L5,11c0,3.4 2.7,6.2 6,6.7L11,21l2,0l0,-3.3c3.3,-0.5 6,-3.3 6,-6.7L17.299999,11.000001z"
+        android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
deleted file mode 100644
index 535e2da..0000000
--- a/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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_ratingbar_indicator_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml
index 207991c..bc339a3 100644
--- a/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml
+++ b/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml
@@ -15,13 +15,15 @@
 -->
 
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:id="@android:id/background"
-          android:drawable="@drawable/abc_ic_star_black_36dp" />
-    <item android:id="@android:id/secondaryProgress"
-          android:drawable="@drawable/abc_ic_star_half_black_36dp" />
+    <item
+        android:id="@android:id/background"
+        android:drawable="@drawable/abc_ic_star_black_36dp"/>
+    <item
+        android:id="@android:id/secondaryProgress"
+        android:drawable="@drawable/abc_ic_star_half_black_36dp"/>
     <item android:id="@android:id/progress">
         <bitmap
-                android:src="@drawable/abc_ic_star_black_36dp"
-                android:tileModeX="repeat"/>
+            android:src="@drawable/abc_ic_star_black_36dp"
+            android:tileModeX="repeat"/>
     </item>
 </layer-list>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_material.xml
new file mode 100644
index 0000000..dde914e
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_material.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@android:id/background"
+        android:drawable="@drawable/abc_ic_star_black_48dp"/>
+    <item
+        android:id="@android:id/secondaryProgress"
+        android:drawable="@drawable/abc_ic_star_half_black_48dp"/>
+    <item android:id="@android:id/progress">
+        <bitmap
+            android:src="@drawable/abc_ic_star_black_48dp"
+            android:tileModeX="repeat"/>
+    </item>
+</layer-list>
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml
similarity index 63%
copy from v7/appcompat/res/values-land/config.xml
copy to v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml
index d0d990d..e2d86c9 100644
--- a/v7/appcompat/res/values-land/config.xml
+++ b/v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <size android:width="@dimen/abc_progress_bar_height_material"
+          android:height="@dimen/abc_progress_bar_height_material"/>
+    <solid android:color="@android:color/white"/>
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
index 2944d98..b3babb2 100644
--- a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
+++ b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
@@ -16,11 +16,12 @@
 
 <ImageView
         xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
         android:id="@+id/action_mode_close_button"
         android:contentDescription="@string/abc_action_mode_done"
         android:focusable="true"
         android:clickable="true"
-        android:src="?attr/actionModeCloseDrawable"
+        app:srcCompat="?attr/actionModeCloseDrawable"
         style="?attr/actionModeCloseButtonStyle"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
index d32ad10..08adfd1 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -28,7 +28,6 @@
     android:paddingTop="4dp"
     android:paddingBottom="4dp"
     android:gravity="bottom"
-    app:allowStacking="@bool/abc_allow_stacked_button_bar"
     style="?attr/buttonBarStyle">
 
     <Button
diff --git a/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
new file mode 100644
index 0000000..a40b6dd
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="?attr/dropdownListPreferredItemHeight"
+             android:minWidth="196dip"
+             android:paddingLeft="16dip"
+             android:paddingRight="16dip">
+
+    <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="?attr/textAppearancePopupMenuHeader"
+            android:layout_gravity="center_vertical"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:textAlignment="viewStart" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
index 47125fe..bf630ff 100644
--- a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
+++ b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
@@ -56,6 +56,16 @@
 
     </RelativeLayout>
 
+    <ImageView
+            android:id="@+id/submenuarrow"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginStart="8dp"
+            android:layout_marginLeft="8dp"
+            android:scaleType="center"
+            android:visibility="gone" />
+
     <!-- Checkbox, and/or radio button will be inserted here. -->
 
 </android.support.v7.view.menu.ListMenuItemView>
diff --git a/v7/appcompat/res/layout/abc_search_view.xml b/v7/appcompat/res/layout/abc_search_view.xml
index a7446e3..b496f5d 100644
--- a/v7/appcompat/res/layout/abc_search_view.xml
+++ b/v7/appcompat/res/layout/abc_search_view.xml
@@ -80,7 +80,6 @@
                   android:layout_height="36dip"
                   android:layout_width="0dp"
                   android:layout_weight="1"
-                  android:minWidth="@dimen/abc_search_view_text_min_width"
                   android:layout_gravity="bottom"
                   android:paddingLeft="@dimen/abc_dropdownitem_text_padding_left"
                   android:paddingRight="@dimen/abc_dropdownitem_text_padding_right"
diff --git a/v7/appcompat/res/layout/notification_media_cancel_action.xml b/v7/appcompat/res/layout/notification_media_cancel_action.xml
index e9b5fb0..c2bd8c2 100644
--- a/v7/appcompat/res/layout/notification_media_cancel_action.xml
+++ b/v7/appcompat/res/layout/notification_media_cancel_action.xml
@@ -24,6 +24,6 @@
     android:layout_marginLeft="2dp"
     android:layout_marginRight="2dp"
     android:layout_weight="1"
-    android:src="@drawable/abc_ic_clear_mtrl_alpha"
+    android:src="@android:drawable/ic_menu_close_clear_cancel"
     android:gravity="center"
     android:visibility="gone"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-b+sr+Latn/strings.xml b/v7/appcompat/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..fc92231
--- /dev/null
+++ b/v7/appcompat/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Gotovo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Odlazak na Početnu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Kretanje nagore"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Još opcija"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skupi"</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">"Pretraga"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Pretražite..."</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Upit za pretragu"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Brisanje upita"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Slanje upita"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovna pretraga"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Izbor aplikacije"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deli sa aplikacijom %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deli sa"</string>
+    <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"&gt;999"</string>
+    <string name="abc_capital_on" msgid="3405795526292276155">"UKLJUČI"</string>
+    <string name="abc_capital_off" msgid="121134116657445385">"ISKLJUČI"</string>
+</resources>
diff --git a/v7/appcompat/res/values-be-rBY/strings.xml b/v7/appcompat/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..d083465
--- /dev/null
+++ b/v7/appcompat/res/values-be-rBY/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    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_up_description" msgid="1594238315039666878">"Перайсці ўверх"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Дадатковыя параметры"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Згарнуць"</string>
+    <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
+    <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Пошук"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Пошук..."</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Запыт на пошук"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Выдалiць запыт"</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_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>
+    <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"больш за 999"</string>
+    <string name="abc_capital_on" msgid="3405795526292276155">"УКЛ."</string>
+    <string name="abc_capital_off" msgid="121134116657445385">"ВЫКЛ."</string>
+</resources>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index 6be164f..07c4b54 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -31,8 +31,8 @@
     <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>
-    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে ভাগ করুন"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s এর সাথে শেয়ার করুন"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে শেয়ার করুন"</string>
     <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"৯৯৯+"</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"চালু"</string>
     <string name="abc_capital_off" msgid="121134116657445385">"বন্ধ"</string>
diff --git a/v7/appcompat/res/values-bs-rBA/strings.xml b/v7/appcompat/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..0daaa5d
--- /dev/null
+++ b/v7/appcompat/res/values-bs-rBA/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Završeno"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Vrati se na početnu stranicu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigiraj prema gore"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Više opcija"</string>
+    <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skupi"</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">"Traži"</string>
+    <string name="abc_search_hint" msgid="7723749260725869598">"Pretraži..."</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Pretraži upit"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Obriši upit"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pošalji upit"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Pretraživanje glasom"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Odaberite aplikaciju"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Vidi sve"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Podijeli sa %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Podijeli sa"</string>
+    <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"&gt;999"</string>
+    <string name="abc_capital_on" msgid="3405795526292276155">"UKLJUČI"</string>
+    <string name="abc_capital_off" msgid="121134116657445385">"ISKLJUČI"</string>
+</resources>
diff --git a/v7/appcompat/res/values-land/bools.xml b/v7/appcompat/res/values-land/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-land/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/bools.xml b/v7/appcompat/res/values-large/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-large/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/config.xml b/v7/appcompat/res/values-large/config.xml
index c4f04a3..58e34a0 100644
--- a/v7/appcompat/res/values-large/config.xml
+++ b/v7/appcompat/res/values-large/config.xml
@@ -20,11 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- Whether action menu items should obey the "withText" showAsAction.
-         This may be set to false for situations where space is
-         extremely limited. -->
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-
     <!-- see comment in values/config.xml -->
     <dimen name="abc_config_prefDialogWidth">440dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values-large/dimens.xml b/v7/appcompat/res/values-large/dimens.xml
index 16bb4f6..5afdda4 100644
--- a/v7/appcompat/res/values-large/dimens.xml
+++ b/v7/appcompat/res/values-large/dimens.xml
@@ -15,13 +15,6 @@
 -->
 
 <resources>
-    <!-- Minimum width of the search view text entry area. -->
-    <dimen name="abc_search_view_text_min_width">192dip</dimen>
-    <!-- The maximum number of action buttons that should be permitted within
-     an action bar/action mode. This will be used to determine how many
-     showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">4</integer>
-
     <item type="dimen" name="abc_dialog_fixed_width_major">60%</item>
     <item type="dimen" name="abc_dialog_fixed_width_minor">90%</item>
     <item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
index 292fcc0..8483786 100644
--- a/v7/appcompat/res/values-ml-rIN/strings.xml
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -19,7 +19,7 @@
     <string name="abc_action_mode_done" msgid="4076576682505996667">"പൂർത്തിയാക്കി"</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_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>
diff --git a/v7/appcompat/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-sw600dp/dimens.xml
index a4bc455..a83abb1 100644
--- a/v7/appcompat/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-sw600dp/dimens.xml
@@ -15,12 +15,6 @@
 -->
 
 <resources>
-
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">5</integer>
-
     <!-- Use the default title sizes on tablets. -->
     <dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
     <!-- Use the default subtitle sizes on tablets. -->
diff --git a/v7/appcompat/res/values-sw720dp/config.xml b/v7/appcompat/res/values-sw720dp/config.xml
new file mode 100644
index 0000000..f36adba
--- /dev/null
+++ b/v7/appcompat/res/values-sw720dp/config.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable cascading submenus. -->
+    <bool name="abc_config_enableCascadingSubmenus">true</bool>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
index 56aecf6..185cd4d 100644
--- a/v7/appcompat/res/values-tr/strings.xml
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -16,7 +16,7 @@
 
 <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">"Tamamlandı"</string>
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Bitti"</string>
     <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>
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
index 4a713a9..79f12f0 100644
--- a/v7/appcompat/res/values-uz-rUZ/strings.xml
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -31,10 +31,8 @@
     <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ovozli qidiruv"</string>
     <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Dastur tanlang"</string>
     <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Barchasini ko‘rish"</string>
-    <!-- String.format failed for translation -->
-    <!-- no translation found for abc_shareactionprovider_share_with_application (7165123711973476752) -->
-    <skip />
-    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bo‘lishish:"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%sga ruxsat berish"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Ruxsat berish"</string>
     <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
     <string name="abc_capital_on" msgid="3405795526292276155">"YONIQ"</string>
     <string name="abc_capital_off" msgid="121134116657445385">"O‘CHIQ"</string>
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index 017ebd1..2098ec7 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -33,8 +33,6 @@
         <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
         <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
 
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
         <!-- Window colors -->
         <item name="android:colorForeground">@color/foreground_material_dark</item>
         <item name="android:colorForegroundInverse">@color/foreground_material_light</item>
@@ -85,8 +83,6 @@
         <item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
         <item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
 
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
         <!-- Window colors -->
         <item name="android:colorForeground">@color/foreground_material_light</item>
         <item name="android:colorForegroundInverse">@color/foreground_material_dark</item>
@@ -146,4 +142,12 @@
     <style name="Base.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog" />
     <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog" />
 
+    <style name="Base.V11.ThemeOverlay.AppCompat.Dialog" parent="Base.V7.ThemeOverlay.AppCompat.Dialog">
+        <item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
+        <item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
+        <item name="android:windowCloseOnTouchOutside">@bool/abc_config_closeDialogWhenTouchOutside</item>
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V11.ThemeOverlay.AppCompat.Dialog" />
+
 </resources>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 8e13da4..d31f6f9 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -152,6 +152,12 @@
         parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
     </style>
 
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+        <item name="android:fontFamily">@string/abc_font_family_title_material</item>
+        <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
     <!-- Search View result styles -->
 
     <style name="Base.TextAppearance.AppCompat.SearchResult.Title"
@@ -180,7 +186,9 @@
 
     <style name="Base.Widget.AppCompat.Button.Borderless" parent="android:Widget.Material.Button.Borderless" />
 
-    <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+    <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored">
+        <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
+    </style>
 
     <style name="Base.Widget.AppCompat.ButtonBar" parent="android:Widget.Material.ButtonBar" />
 
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
index b517723..4b479be 100644
--- a/v7/appcompat/res/values-v21/themes_base.xml
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -164,4 +164,10 @@
 
     <style name="Platform.ThemeOverlay.AppCompat.Light" />
 
+    <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V21.ThemeOverlay.AppCompat.Dialog" />
+
+    <style name="Base.V21.ThemeOverlay.AppCompat.Dialog" parent="Base.V11.ThemeOverlay.AppCompat.Dialog">
+        <item name="android:windowElevation">@dimen/abc_floating_window_z</item>
+    </style>
+
 </resources>
diff --git a/v7/appcompat/res/values-v23/styles_base.xml b/v7/appcompat/res/values-v23/styles_base.xml
index 56563c7..e6be6b9 100644
--- a/v7/appcompat/res/values-v23/styles_base.xml
+++ b/v7/appcompat/res/values-v23/styles_base.xml
@@ -17,6 +17,8 @@
 
 <resources>
 
+    <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+
     <style name="Base.Widget.AppCompat.Button.Colored" parent="android:Widget.Material.Button.Colored" />
 
     <style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.Material.RatingBar.Indicator" />
diff --git a/v7/appcompat/res/values-w360dp/dimens.xml b/v7/appcompat/res/values-w360dp/dimens.xml
deleted file mode 100644
index e5b2456..0000000
--- a/v7/appcompat/res/values-w360dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">3</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w480dp/config.xml b/v7/appcompat/res/values-w480dp/config.xml
deleted file mode 100644
index e95b6ff..0000000
--- a/v7/appcompat/res/values-w480dp/config.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-w500dp/dimens.xml b/v7/appcompat/res/values-w500dp/dimens.xml
deleted file mode 100644
index dd6458b..0000000
--- a/v7/appcompat/res/values-w500dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">4</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w600dp/dimens.xml b/v7/appcompat/res/values-w600dp/dimens.xml
deleted file mode 100644
index 252ba6a..0000000
--- a/v7/appcompat/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">5</integer>
-
-    <!-- Minimum width of the search view text entry area. -->
-    <dimen name="abc_search_view_text_min_width">192dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge/bools.xml b/v7/appcompat/res/values-xlarge/bools.xml
deleted file mode 100644
index 05c5aab..0000000
--- a/v7/appcompat/res/values-xlarge/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge/dimens.xml b/v7/appcompat/res/values-xlarge/dimens.xml
index 0dd244a..f0d560d 100644
--- a/v7/appcompat/res/values-xlarge/dimens.xml
+++ b/v7/appcompat/res/values-xlarge/dimens.xml
@@ -15,15 +15,6 @@
 -->
 
 <resources>
-
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">5</integer>
-
-    <!-- Minimum width of the search view text entry area. -->
-    <dimen name="abc_search_view_text_min_width">192dip</dimen>
-
     <item type="dimen" name="abc_dialog_fixed_width_major">50%</item>
     <item type="dimen" name="abc_dialog_fixed_width_minor">70%</item>
     <item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 6553b6e..aee7079 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -174,6 +174,8 @@
         <attr name="textAppearanceLargePopupMenu" format="reference"/>
         <!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
         <attr name="textAppearanceSmallPopupMenu" format="reference"/>
+        <!-- Text color, typeface, size, and style for header text inside of a popup menu. -->
+        <attr name="textAppearancePopupMenuHeader" format="reference" />
 
 
         <!-- =================== -->
@@ -331,6 +333,9 @@
         <!-- The background used by framework controls. -->
         <attr name="controlBackground" format="reference" />
 
+        <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->
+        <attr name="colorBackgroundFloating" format="color" />
+
         <!-- ============ -->
         <!-- Alert Dialog styles -->
         <!-- ============ -->
@@ -385,6 +390,8 @@
         <!-- Default style for the Switch widget. -->
         <attr name="switchStyle" format="reference" />
 
+        <!-- Default menu-style ListView style. -->
+        <attr name="listMenuViewStyle" format="reference" />
     </declare-styleable>
 
 
@@ -560,6 +567,8 @@
         <attr name="android:itemIconDisabledAlpha"/>
         <!-- Whether space should be reserved in layout when an icon is missing. -->
         <attr name="preserveIconSpacing" format="boolean" />
+        <!-- Drawable for the arrow icon indicating a particular item is a submenu. -->
+        <attr name="subMenuArrow" format="reference" />
     </declare-styleable>
     <declare-styleable name="ActionMenuView">
         <!-- Size of padding on either end of a divider. -->
@@ -801,17 +810,39 @@
         <attr name="title" />
         <attr name="subtitle" />
         <attr name="android:gravity" />
-        <attr name="titleMargins" format="dimension" />
+        <!--  Specifies extra space on the left, start, right and end sides
+              of the toolbar's title. Margin values should be positive. -->
+        <attr name="titleMargin" format="dimension" />
+        <!--  Specifies extra space on the start side of the toolbar's title.
+              If both this attribute and titleMargin are specified, then this
+              attribute takes precedence. Margin values should be positive. -->
         <attr name="titleMarginStart" format="dimension" />
+        <!--  Specifies extra space on the end side of the toolbar's title.
+              If both this attribute and titleMargin are specified, then this
+              attribute takes precedence. Margin values should be positive. -->
         <attr name="titleMarginEnd" format="dimension" />
+        <!--  Specifies extra space on the top side of the toolbar's title.
+              If both this attribute and titleMargin are specified, then this
+              attribute takes precedence. Margin values should be positive. -->
         <attr name="titleMarginTop" format="dimension" />
+        <!--  Specifies extra space on the bottom side of the toolbar's title.
+              If both this attribute and titleMargin are specified, then this
+              attribute takes precedence. Margin values should be positive. -->
         <attr name="titleMarginBottom" format="dimension" />
+        <!-- {@deprecated Use titleMargin} -->
+        <attr name="titleMargins" format="dimension" />
         <attr name="contentInsetStart" />
         <attr name="contentInsetEnd" />
         <attr name="contentInsetLeft" />
         <attr name="contentInsetRight" />
         <attr name="maxButtonHeight" format="dimension" />
-
+        <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" />
@@ -824,10 +855,6 @@
         <!-- Text to set as the content description for the navigation button
              located at the start of the toolbar. -->
         <attr name="navigationContentDescription" format="string" />
-
-        <!-- Allows us to read in the minHeight attr pre-v16 -->
-        <attr name="android:minHeight" />
-
         <!-- Drawable to set as the logo that appears at the starting side of
              the Toolbar, just after the navigation button. -->
         <attr name="logo" />
@@ -838,6 +865,7 @@
         <attr name="titleTextColor" format="color" />
         <!-- A color to apply to the subtitle string. -->
         <attr name="subtitleTextColor" format="color" />
+        <attr name="android:minHeight" />
     </declare-styleable>
 
     <declare-styleable name="PopupWindowBackgroundState">
@@ -965,10 +993,47 @@
         <attr name="allowStacking" format="boolean" />
     </declare-styleable>
 
+    <!-- Attributes that can be assigned to a ColorStateList item. -->
+    <declare-styleable name="ColorStateListItem">
+        <!-- Base color for this state. -->
+        <attr name="android:color" />
+        <!-- Alpha multiplier applied to the base color. -->
+        <attr name="alpha" format="float" />
+        <attr name="android:alpha"/>
+    </declare-styleable>
+
     <declare-styleable name="AppCompatImageView">
         <attr name="android:src"/>
         <!-- TODO -->
         <attr name="srcCompat" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="AppCompatSeekBar">
+        <attr name="android:thumb" />
+        <!-- Drawable displayed at each progress position on a seekbar. -->
+        <attr name="tickMark" format="reference" />
+        <!-- Tint to apply to the tick mark drawable. -->
+        <attr name="tickMarkTint" format="color" />
+        <!-- Blending mode used to apply the tick mark tint. -->
+        <attr name="tickMarkTintMode">
+            <!-- The tint is drawn on top of the drawable.
+                 [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+            <enum name="src_over" value="3" />
+            <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+                 color channels are thrown out. [Sa * Da, Sc * Da] -->
+            <enum name="src_in" value="5" />
+            <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+                 channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+            <enum name="src_atop" value="9" />
+            <!-- Multiplies the color and alpha channels of the drawable with those of
+                 the tint. [Sa * Da, Sc * Dc] -->
+            <enum name="multiply" value="14" />
+            <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+            <enum name="screen" value="15" />
+            <!-- Combines the tint and drawable color and alpha channels, clamping the
+                 result to valid color values. Saturate(S + D) -->
+            <enum name="add" value="16" />
+        </attr>
+    </declare-styleable>
+
 </resources>
diff --git a/v7/appcompat/res/values/bools.xml b/v7/appcompat/res/values/bools.xml
index 33d4948..825ece0 100644
--- a/v7/appcompat/res/values/bools.xml
+++ b/v7/appcompat/res/values/bools.xml
@@ -17,8 +17,6 @@
 <resources>
 
     <bool name="abc_action_bar_embed_tabs">true</bool>
-    <bool name="abc_action_bar_embed_tabs_pre_jb">false</bool>
-    <bool name="abc_action_bar_expanded_action_views_exclusive">true</bool>
 
     <bool name="abc_config_showMenuShortcutsWhenKeyboardPresent">false</bool>
 
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index e0c521b..e682eb1 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -17,11 +17,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- Whether action menu items should obey the "withText" showAsAction
-    flag. This may be set to false for situations where space is
-    extremely limited. -->
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">false</bool>
-
     <!-- The maximum width we would prefer dialogs to be.  0 if there is no
     maximum (let them grow as large as the screen).  Actual values are
     specified for -large and -xlarge configurations. -->
@@ -38,6 +33,10 @@
 
     <bool name="abc_config_closeDialogWhenTouchOutside">true</bool>
 
+    <!-- Whether to open UI submenus side by side with the top menu (as opposed to
+         replacing the top menu). -->
+    <bool name="abc_config_enableCascadingSubmenus">false</bool>
+
     <!-- Maximum numerical value that will be shown in a status bar
          notification icon or in the notification itself. Will be replaced
          with @string/status_bar_notification_info_overflow when shown in the
@@ -45,4 +44,4 @@
     <integer name="status_bar_notification_info_maxnum">999</integer>
 
     <integer name="cancel_button_image_alpha">127</integer>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 37130e3..3eeeb5d 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -15,12 +15,6 @@
 -->
 
 <resources>
-
-    <!-- The maximum number of action buttons that should be permitted within
-         an action bar/action mode. This will be used to determine how many
-         showAsAction="ifRoom" items can fit. "always" items can override this. -->
-    <integer name="abc_max_action_buttons">2</integer>
-
     <!-- Maximum width for a stacked action bar tab. This prevents
          action bar tabs from becoming too wide on a wide screen when only
          a few are present. -->
@@ -33,8 +27,6 @@
 
     <dimen name="abc_panel_menu_list_width">296dp</dimen>
 
-    <!-- Minimum width of the search view text entry area. -->
-    <dimen name="abc_search_view_text_min_width">160dip</dimen>
     <!-- Preferred width of the search view. -->
     <dimen name="abc_search_view_preferred_width">320dip</dimen>
 
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
index 357dc3e..4fb8eae 100644
--- a/v7/appcompat/res/values/dimens_material.xml
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -54,6 +54,7 @@
     <dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
     <dimen name="abc_text_size_subtitle_material_toolbar">16dp</dimen>
     <dimen name="abc_text_size_menu_material">16sp</dimen>
+    <dimen name="abc_text_size_menu_header_material">14sp</dimen>
     <dimen name="abc_text_size_body_2_material">14sp</dimen>
     <dimen name="abc_text_size_body_1_material">14sp</dimen>
     <dimen name="abc_text_size_caption_material">12sp</dimen>
@@ -71,4 +72,6 @@
     <dimen name="abc_seekbar_track_background_height_material">2dp</dimen>
     <dimen name="abc_seekbar_track_progress_height_material">2dp</dimen>
 
+    <dimen name="abc_progress_bar_height_material">4dp</dimen>
+
 </resources>
diff --git a/v7/appcompat/res/values/donottranslate_material.xml b/v7/appcompat/res/values/donottranslate_material.xml
new file mode 100644
index 0000000..3f01f7a
--- /dev/null
+++ b/v7/appcompat/res/values/donottranslate_material.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+
+    <string name="abc_font_family_display_4_material">sans-serif-light</string>
+    <string name="abc_font_family_display_3_material">sans-serif</string>
+    <string name="abc_font_family_display_2_material">sans-serif</string>
+    <string name="abc_font_family_display_1_material">sans-serif</string>
+    <string name="abc_font_family_headline_material">sans-serif</string>
+    <string name="abc_font_family_title_material">sans-serif-medium</string>
+    <string name="abc_font_family_subhead_material">sans-serif</string>
+    <string name="abc_font_family_menu_material">sans-serif</string>
+    <string name="abc_font_family_body_2_material">sans-serif-medium</string>
+    <string name="abc_font_family_body_1_material">sans-serif</string>
+    <string name="abc_font_family_caption_material">sans-serif</string>
+    <string name="abc_font_family_button_material">sans-serif-medium</string>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 525f4ed..ad86cbd 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -143,34 +143,22 @@
            parent="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
     </style>
 
-    <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu">
-    </style>
+    <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu" />
 
-    <style name="Widget.AppCompat.Light.PopupMenu"
-           parent="Base.Widget.AppCompat.Light.PopupMenu">
-    </style>
+    <style name="Widget.AppCompat.Light.PopupMenu" parent="Base.Widget.AppCompat.Light.PopupMenu" />
 
-    <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu">
-    </style>
+    <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu" />
+
+    <style name="Widget.AppCompat.ListMenuView" parent="Base.Widget.AppCompat.ListMenuView" />
 
     <style name="Widget.AppCompat.PopupWindow" parent="Base.Widget.AppCompat.PopupWindow">
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large"
-           parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large">
-    </style>
+    <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" />
 
-    <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small"
-           parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small">
-    </style>
+    <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" />
 
-    <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large"
-           parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small"
-           parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small">
-    </style>
+    <style name="TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" />
 
     <style name="TextAppearance.AppCompat.SearchResult.Title"
            parent="Base.TextAppearance.AppCompat.SearchResult.Title">
@@ -231,6 +219,8 @@
 
     <style name="Widget.AppCompat.SeekBar" parent="Base.Widget.AppCompat.SeekBar" />
 
+    <style name="Widget.AppCompat.SeekBar.Discrete" parent="Base.Widget.AppCompat.SeekBar.Discrete" />
+
     <!-- Toolbar -->
 
     <style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
@@ -329,6 +319,8 @@
     <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" />
+    <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Widget.PopupMenu.Large" />
+    <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Widget.PopupMenu.Small" />
 
     <!-- These styles didn't exist on v7. Since we only use the media template in later versions
          (ICS+), just define it here and use the correct references in values/v14 -->
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index 123461f..0565343 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -75,7 +75,7 @@
     </style>
 
     <style name="Base.Widget.AppCompat.ActionButton.Overflow" parent="RtlUnderlay.Widget.AppCompat.ActionButton.Overflow">
-        <item name="android:src">@drawable/abc_ic_menu_moreoverflow_mtrl_alpha</item>
+        <item name="srcCompat">@drawable/abc_ic_menu_overflow_material</item>
         <item name="android:background">?attr/actionBarItemBackground</item>
         <item name="android:contentDescription">@string/abc_action_menu_overflow_description</item>
         <item name="android:minWidth">@dimen/abc_action_button_min_width_overflow_material</item>
@@ -209,6 +209,10 @@
         <item name="android:divider">@null</item>
     </style>
 
+    <style name="Base.Widget.AppCompat.ListMenuView" parent="android:Widget">
+        <item name="subMenuArrow">@drawable/abc_ic_arrow_drop_right_black_24dp</item>
+    </style>
+
     <style name="Base.TextAppearance.AppCompat.Widget.DropDownItem"
            parent="android:TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
@@ -248,16 +252,13 @@
     <style name="Base.Widget.AppCompat.Light.PopupMenu" parent="@style/Widget.AppCompat.ListPopupWindow">
     </style>
 
-    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
-    </style>
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu"/>
 
-    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
-    </style>
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu"/>
 
-    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
-    </style>
-
-    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+        <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="Base.TextAppearance.AppCompat.SearchResult" parent="">
@@ -300,8 +301,9 @@
         <item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
         <item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
         <item name="android:minHeight">?attr/actionBarSize</item>
-        <item name="titleMargins">4dp</item>
+        <item name="titleMargin">4dp</item>
         <item name="maxButtonHeight">@dimen/abc_action_bar_default_height_material</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>
@@ -327,11 +329,11 @@
         <item name="layout">@layout/abc_search_view</item>
         <item name="queryBackground">@drawable/abc_textfield_search_material</item>
         <item name="submitBackground">@drawable/abc_textfield_search_material</item>
-        <item name="closeIcon">@drawable/abc_ic_clear_mtrl_alpha</item>
-        <item name="searchIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
-        <item name="searchHintIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
-        <item name="goIcon">@drawable/abc_ic_go_search_api_mtrl_alpha</item>
-        <item name="voiceIcon">@drawable/abc_ic_voice_search_api_mtrl_alpha</item>
+        <item name="closeIcon">@drawable/abc_ic_clear_material</item>
+        <item name="searchIcon">@drawable/abc_ic_search_api_material</item>
+        <item name="searchHintIcon">@drawable/abc_ic_search_api_material</item>
+        <item name="goIcon">@drawable/abc_ic_go_search_api_material</item>
+        <item name="voiceIcon">@drawable/abc_ic_voice_search_api_material</item>
         <item name="commitIcon">@drawable/abc_ic_commit_search_api_mtrl_alpha</item>
         <item name="suggestionRowLayout">@layout/abc_search_dropdown_item_icons_2line</item>
     </style>
@@ -391,8 +393,8 @@
     <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>
+        <item name="android:progressDrawable">@drawable/abc_ratingbar_material</item>
+        <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_material</item>
     </style>
 
     <style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.RatingBar">
@@ -423,6 +425,11 @@
         <item name="android:paddingRight">16dip</item>
     </style>
 
+    <!-- A seek bar with tick marks at each progress value. -->
+    <style name="Base.Widget.AppCompat.SeekBar.Discrete">
+        <item name="tickMark">@drawable/abc_seekbar_tick_mark_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>
@@ -453,7 +460,7 @@
 
     <!-- Colored borderless ink button -->
     <style name="Base.Widget.AppCompat.Button.Borderless.Colored">
-        <item name="android:textColor">?attr/colorAccent</item>
+        <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
     </style>
 
     <style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
diff --git a/v7/appcompat/res/values/themes.xml b/v7/appcompat/res/values/themes.xml
index f8962df..28ea38e 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -92,4 +92,7 @@
          text color. -->
     <style name="ThemeOverlay.AppCompat.Dark.ActionBar" parent="Base.ThemeOverlay.AppCompat.Dark.ActionBar" />
 
+    <style name="ThemeOverlay.AppCompat.Dialog" parent="Base.ThemeOverlay.AppCompat.Dialog" />
+    <style name="ThemeOverlay.AppCompat.Dialog.Alert" parent="Base.ThemeOverlay.AppCompat.Dialog.Alert" />
+
 </resources>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 38757df..3373423 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -117,13 +117,15 @@
         <item name="windowActionModeOverlay">false</item>
         <item name="actionBarPopupTheme">@null</item>
 
+        <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
+
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">false</item>
 
         <item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
         <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
         <item name="borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material</item>
 
         <item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
         <item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
@@ -152,14 +154,14 @@
         <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
         <item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
         <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
-        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_material</item>
         <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
 
-        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
-        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
-        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
-        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
+        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_material</item>
+        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_material</item>
+        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_material</item>
+        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_material</item>
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material</item>
 
         <!-- Panel attributes -->
         <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
@@ -190,8 +192,10 @@
         <item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
         <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
         <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
+        <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
         <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
         <item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+        <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
 
         <!-- SearchView attributes -->
         <item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
@@ -249,10 +253,10 @@
         <item name="buttonBarNeutralButtonStyle">?attr/buttonBarButtonStyle</item>
 
         <!-- Dialog attributes -->
-        <item name="dialogTheme">@style/Theme.AppCompat.Dialog</item>
+        <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
         <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
 
-        <item name="alertDialogTheme">@style/Theme.AppCompat.Dialog.Alert</item>
+        <item name="alertDialogTheme">@style/ThemeOverlay.AppCompat.Dialog.Alert</item>
         <item name="alertDialogStyle">@style/AlertDialog.AppCompat</item>
         <item name="alertDialogCenterButtons">false</item>
         <item name="textColorAlertDialogListItem">@color/abc_primary_text_material_dark</item>
@@ -274,13 +278,15 @@
         <item name="windowActionModeOverlay">false</item>
         <item name="actionBarPopupTheme">@null</item>
 
+        <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
+
         <!-- Used by MediaRouter -->
         <item name="isLightTheme">true</item>
 
         <item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
         <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
         <item name="borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material</item>
 
         <item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
         <item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
@@ -306,14 +312,14 @@
         <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
         <item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
         <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
-        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+        <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_material</item>
         <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
 
-        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
-        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
-        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
-        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
+        <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_material</item>
+        <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_material</item>
+        <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_material</item>
+        <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_material</item>
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material</item>
 
         <!-- Dropdown Spinner Attributes -->
         <item name="actionDropDownStyle">@style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar</item>
@@ -347,8 +353,10 @@
         <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
         <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
         <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
+        <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
         <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
         <item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+        <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
 
         <!-- SearchView attributes -->
         <item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
@@ -406,7 +414,7 @@
         <item name="buttonBarNeutralButtonStyle">?attr/buttonBarButtonStyle</item>
 
         <!-- Dialog attributes -->
-        <item name="dialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
+        <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
         <item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
 
         <item name="alertDialogTheme">@style/Theme.AppCompat.Light.Dialog.Alert</item>
@@ -449,13 +457,13 @@
     </style>
 
     <style name="Base.V7.Theme.AppCompat.Dialog" parent="Base.Theme.AppCompat">
-        <item name="android:colorBackground">@color/background_floating_material_dark</item>
+        <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
 
         <item name="android:windowFrame">@null</item>
         <item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
         <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
-        <item name="android:windowBackground">@drawable/abc_dialog_material_background_dark</item>
+        <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
         <item name="android:windowIsFloating">true</item>
         <item name="android:backgroundDimEnabled">true</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -472,13 +480,13 @@
     </style>
 
     <style name="Base.V7.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light">
-        <item name="android:colorBackground">@color/background_floating_material_light</item>
+        <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
 
         <item name="android:windowFrame">@null</item>
         <item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
         <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
-        <item name="android:windowBackground">@drawable/abc_dialog_material_background_light</item>
+        <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
         <item name="android:windowIsFloating">true</item>
         <item name="android:backgroundDimEnabled">true</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -627,4 +635,36 @@
         <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
     </style>
 
+    <!-- Theme overlay that overrides window properties to display as a dialog. -->
+    <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V7.ThemeOverlay.AppCompat.Dialog" />
+
+    <style name="Base.ThemeOverlay.AppCompat.Dialog.Alert">
+        <item name="windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item>
+        <item name="windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item>
+    </style>
+
+    <!-- Theme overlay that overrides window properties to display as a dialog. -->
+    <style name="Base.V7.ThemeOverlay.AppCompat.Dialog" parent="Base.ThemeOverlay.AppCompat">
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
+
+        <item name="android:windowFrame">@null</item>
+        <item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
+        <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
+        <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowAnimationStyle">@style/Animation.AppCompat.Dialog</item>
+        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
+
+        <item name="windowActionBar">false</item>
+        <item name="windowActionModeOverlay">true</item>
+
+        <item name="listPreferredItemPaddingLeft">24dip</item>
+        <item name="listPreferredItemPaddingRight">24dip</item>
+
+        <item name="android:listDivider">@null</item>
+    </style>
+
 </resources>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index efa3eb2..8c4f82f 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -516,16 +516,15 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        final int keyCode = event.getKeyCode();
-        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+        if (KeyEventCompat.isCtrlPressed(event) &&
+                event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
+            // Capture the Control-< and send focus to the ActionBar
             final int action = event.getAction();
             if (action == KeyEvent.ACTION_DOWN) {
-                if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
-                    final ActionBar actionBar = getSupportActionBar();
-                    if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
-                        mEatKeyUpEvent = true;
-                        return true;
-                    }
+                final ActionBar actionBar = getSupportActionBar();
+                if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
+                    mEatKeyUpEvent = true;
+                    return true;
                 }
             } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
                 mEatKeyUpEvent = false;
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
index f16cb8b..46ec486 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
@@ -28,6 +28,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.FragmentActivity;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.view.ActionMode;
@@ -180,7 +181,9 @@
     private static AppCompatDelegate create(Context context, Window window,
             AppCompatCallback callback) {
         final int sdk = Build.VERSION.SDK_INT;
-        if (sdk >= 23) {
+        if (BuildCompat.isAtLeastN()) {
+            return new AppCompatDelegateImplN(context, window, callback);
+        } else if (sdk >= 23) {
             return new AppCompatDelegateImplV23(context, window, callback);
         } else if (sdk >= 14) {
             return new AppCompatDelegateImplV14(context, window, callback);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
index f822c45..5fe7a83 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.v7.appcompat.R;
@@ -25,6 +26,7 @@
 import android.support.v7.view.SupportMenuInflater;
 import android.support.v7.view.WindowCallbackWrapper;
 import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.widget.AppCompatDrawableManager;
 import android.support.v7.widget.TintTypedArray;
 import android.view.KeyEvent;
 import android.view.Menu;
@@ -34,6 +36,8 @@
 
 abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
 
+    private static final int[] sWindowBackgroundStyleable = {android.R.attr.windowBackground};
+
     final Context mContext;
     final Window mWindow;
     final Window.Callback mOriginalWindowCallback;
@@ -73,6 +77,14 @@
         mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
         // Now install the new callback
         mWindow.setCallback(mAppCompatWindowCallback);
+
+        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+                context, null, sWindowBackgroundStyleable);
+        final Drawable winBg = a.getDrawableIfKnown(0);
+        if (winBg != null) {
+            mWindow.setBackgroundDrawable(winBg);
+        }
+        a.recycle();
     }
 
     abstract void initWindowDecorActionBar();
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
new file mode 100644
index 0000000..d840ca6
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
@@ -0,0 +1,55 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.view.KeyboardShortcutGroup;
+import android.view.Menu;
+import android.view.Window;
+
+import java.util.List;
+
+class AppCompatDelegateImplN extends AppCompatDelegateImplV23 {
+
+    AppCompatDelegateImplN(Context context, Window window, AppCompatCallback callback) {
+        super(context, window, callback);
+    }
+
+    @Override
+    Window.Callback wrapWindowCallback(Window.Callback callback) {
+        return new AppCompatWindowCallbackN(callback);
+    }
+
+    class AppCompatWindowCallbackN extends AppCompatWindowCallbackV23 {
+        AppCompatWindowCallbackN(Window.Callback callback) {
+            super(callback);
+        }
+
+        @Override
+        public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
+            final PanelFeatureState panel = getPanelState(Window.FEATURE_OPTIONS_PANEL, true);
+            if (panel != null && panel.menu != null) {
+                // The menu provided is one created by PhoneWindow which we don't actually use.
+                // Instead we'll pass through our own...
+                super.onProvideKeyboardShortcuts(data, panel.menu);
+            } else {
+                // If we don't have a menu, jump pass through the original instead
+                super.onProvideKeyboardShortcuts(data, menu);
+            }
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 34c0aca..43e1f7b 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -1516,7 +1516,7 @@
         return null;
     }
 
-    private PanelFeatureState getPanelState(int featureId, boolean required) {
+    protected PanelFeatureState getPanelState(int featureId, boolean required) {
         PanelFeatureState[] ar;
         if ((ar = mPanels) == null || ar.length <= featureId) {
             PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
@@ -1819,7 +1819,7 @@
         }
     }
 
-    private static final class PanelFeatureState {
+    protected static final class PanelFeatureState {
 
         /** Feature ID for this panel. */
         int featureId;
diff --git a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
index 0e50cc1..7d4f403 100644
--- a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
+++ b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
@@ -17,11 +17,14 @@
 package android.support.v7.view;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.support.v4.content.res.ConfigurationHelper;
 import android.support.v4.view.ViewConfigurationCompat;
 import android.support.v7.appcompat.R;
+import android.util.DisplayMetrics;
 import android.view.ViewConfiguration;
 
 /**
@@ -42,8 +45,31 @@
         mContext = context;
     }
 
+    /**
+     * Returns the maximum number of action buttons that should be permitted within an action
+     * bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit.
+     * "always" items can override this.
+     */
     public int getMaxActionButtons() {
-        return mContext.getResources().getInteger(R.integer.abc_max_action_buttons);
+        final Resources res = mContext.getResources();
+        final int widthDp = ConfigurationHelper.getScreenWidthDp(res);
+        final int heightDp = ConfigurationHelper.getScreenHeightDp(res);
+        final int smallest = ConfigurationHelper.getSmallestScreenWidthDp(res);
+
+        if (smallest > 600 || widthDp > 600 || (widthDp > 960 && heightDp > 720)
+                || (widthDp > 720 && heightDp > 960)) {
+            // For values-w600dp, values-sw600dp and values-xlarge.
+            return 5;
+        } else if (widthDp >= 500 || (widthDp > 640 && heightDp > 480)
+                || (widthDp > 480 && heightDp > 640)) {
+            // For values-w500dp and values-large.
+            return 4;
+        } else if (widthDp >= 360) {
+            // For values-w360dp.
+            return 3;
+        } else {
+            return 2;
+        }
     }
 
     public boolean showsOverflowMenuButton() {
@@ -59,14 +85,7 @@
     }
 
     public boolean hasEmbeddedTabs() {
-        final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
-        if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
-            return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
-        }
-
-        // The embedded tabs policy changed in Jellybean; give older apps the old policy
-        // so they get what they expect.
-        return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb);
+        return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
     }
 
     public int getTabContainerHeight() {
diff --git a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
index 45278bc..ade266f 100644
--- a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
+++ b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
@@ -18,6 +18,7 @@
 
 import android.view.ActionMode;
 import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -27,6 +28,8 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.util.List;
+
 /**
  * A simple decorator stub for Window.Callback that passes through any calls
  * to the wrapped instance as a base implementation. Call super.foo() to call into
@@ -159,4 +162,9 @@
     public void onActionModeFinished(ActionMode mode) {
         mWrapped.onActionModeFinished(mode);
     }
+
+    @Override
+    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, Menu menu) {
+        mWrapped.onProvideKeyboardShortcuts(data, menu);
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
index 0b80cf2..445aff4 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
@@ -23,14 +23,17 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.v4.content.res.ConfigurationHelper;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.widget.ActionMenuView;
 import android.support.v7.widget.AppCompatTextView;
+import android.support.v7.widget.ForwardingListener;
 import android.support.v7.widget.ListPopupWindow;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -49,7 +52,7 @@
     private CharSequence mTitle;
     private Drawable mIcon;
     private MenuBuilder.ItemInvoker mItemInvoker;
-    private ListPopupWindow.ForwardingListener mForwardingListener;
+    private ForwardingListener mForwardingListener;
     private PopupCallback mPopupCallback;
 
     private boolean mAllowTextWithIcon;
@@ -71,8 +74,7 @@
     public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         final Resources res = context.getResources();
-        mAllowTextWithIcon = res.getBoolean(
-                R.bool.abc_config_allowActionMenuItemTextWithIcon);
+        mAllowTextWithIcon = shouldAllowTextWithIcon();
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.ActionMenuItemView, defStyle, 0);
         mMinWidth = a.getDimensionPixelSize(
@@ -93,11 +95,23 @@
             super.onConfigurationChanged(newConfig);
         }
 
-        mAllowTextWithIcon = getContext().getResources().getBoolean(
-                R.bool.abc_config_allowActionMenuItemTextWithIcon);
+        mAllowTextWithIcon = shouldAllowTextWithIcon();
         updateTextButtonVisibility();
     }
 
+    /**
+     * Whether action menu items should obey the "withText" showAsAction flag. This may be set to
+     * false for situations where space is extremely limited. -->
+     */
+    private boolean shouldAllowTextWithIcon() {
+        final Configuration config = getContext().getResources().getConfiguration();
+        final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
+        final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
+
+        return widthDp >= 480 || (widthDp >= 640 && heightDp >= 480)
+                || config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+    }
+
     @Override
     public void setPadding(int l, int t, int r, int b) {
         mSavedPaddingLeft = l;
@@ -291,13 +305,13 @@
         }
     }
 
-    private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener {
+    private class ActionMenuItemForwardingListener extends ForwardingListener {
         public ActionMenuItemForwardingListener() {
             super(ActionMenuItemView.this);
         }
 
         @Override
-        public ListPopupWindow getPopup() {
+        public ShowableListMenu getPopup() {
             if (mPopupCallback != null) {
                 return mPopupCallback.getPopup();
             }
@@ -308,7 +322,7 @@
         protected boolean onForwardingStarted() {
             // Call the invoker, then check if the expected popup is showing.
             if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
-                final ListPopupWindow popup = getPopup();
+                final ShowableListMenu popup = getPopup();
                 return popup != null && popup.isShowing();
             }
             return false;
@@ -323,6 +337,6 @@
     }
 
     public static abstract class PopupCallback {
-        public abstract ListPopupWindow getPopup();
+        public abstract ShowableListMenu getPopup();
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
new file mode 100644
index 0000000..9843f5e
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -0,0 +1,727 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.annotation.AttrRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuItemHoverListener;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AbsListView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
+ * side.
+ */
+final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
+        PopupWindow.OnDismissListener {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
+    public @interface HorizPosition {}
+
+    private static final int HORIZ_POSITION_LEFT = 0;
+    private static final int HORIZ_POSITION_RIGHT = 1;
+
+    /**
+     * Delay between hovering over a menu item with a mouse and receiving
+     * side-effects (ex. opening a sub-menu or closing unrelated menus).
+     */
+    private static final int SUBMENU_TIMEOUT_MS = 200;
+
+    private final Context mContext;
+    private final int mMenuMaxWidth;
+    private final int mPopupStyleAttr;
+    private final int mPopupStyleRes;
+    private final boolean mOverflowOnly;
+    private final Handler mSubMenuHoverHandler;
+
+    /** List of menus that were added before this popup was shown. */
+    private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+
+    /**
+     * List of open menus. The first item is the root menu and each
+     * subsequent item is a direct submenu of the previous item.
+     */
+    private final List<CascadingMenuInfo> mShowingMenus = new ArrayList<>();
+
+    private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+        @Override
+        public void onGlobalLayout() {
+            // Only move the popup if it's showing and non-modal. We don't want
+            // to be moving around the only interactive window, since there's a
+            // good chance the user is interacting with it.
+            if (isShowing() && mShowingMenus.size() > 0
+                    && !mShowingMenus.get(0).window.isModal()) {
+                final View anchor = mShownAnchorView;
+                if (anchor == null || !anchor.isShown()) {
+                    dismiss();
+                } else {
+                    // Recompute window sizes and positions.
+                    for (CascadingMenuInfo info : mShowingMenus) {
+                        info.window.show();
+                    }
+                }
+            }
+        }
+    };
+
+    private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() {
+        @Override
+        public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+            // If the mouse moves between two windows, hover enter/exit pairs
+            // may be received out of order. So, instead of canceling all
+            // pending runnables, only cancel runnables for the host menu.
+            mSubMenuHoverHandler.removeCallbacksAndMessages(menu);
+        }
+
+        @Override
+        public void onItemHoverEnter(
+                @NonNull final MenuBuilder menu, @NonNull final MenuItem item) {
+            // Something new was hovered, cancel all scheduled runnables.
+            mSubMenuHoverHandler.removeCallbacksAndMessages(null);
+
+            // Find the position of the hovered menu within the added menus.
+            int menuIndex = -1;
+            for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+                if (menu == mShowingMenus.get(i).menu) {
+                    menuIndex = i;
+                    break;
+                }
+            }
+
+            if (menuIndex == -1) {
+                return;
+            }
+
+            final CascadingMenuInfo nextInfo;
+            final int nextIndex = menuIndex + 1;
+            if (nextIndex < mShowingMenus.size()) {
+                nextInfo = mShowingMenus.get(nextIndex);
+            } else {
+                nextInfo = null;
+            }
+
+            final Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    // Close any other submenus that might be open at the
+                    // current or a deeper level.
+                    if (nextInfo != null) {
+                        // Disable exit animations to prevent overlapping
+                        // fading out submenus.
+                        mShouldCloseImmediately = true;
+                        nextInfo.menu.close(false /* closeAllMenus */);
+                        mShouldCloseImmediately = false;
+                    }
+
+                    // Then open the selected submenu, if there is one.
+                    if (item.isEnabled() && item.hasSubMenu()) {
+                        menu.performItemAction(item, 0);
+                    }
+                }
+            };
+            final long uptimeMillis = SystemClock.uptimeMillis() + SUBMENU_TIMEOUT_MS;
+            mSubMenuHoverHandler.postAtTime(runnable, menu, uptimeMillis);
+        }
+    };
+
+    private int mRawDropDownGravity = Gravity.NO_GRAVITY;
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+    private View mAnchorView;
+    private View mShownAnchorView;
+    private int mLastPosition;
+    private int mInitXOffset;
+    private int mInitYOffset;
+    private boolean mForceShowIcon;
+    private boolean mShowTitle;
+    private Callback mPresenterCallback;
+    private ViewTreeObserver mTreeObserver;
+    private PopupWindow.OnDismissListener mOnDismissListener;
+
+    /** Whether popup menus should disable exit animations when closing. */
+    private boolean mShouldCloseImmediately;
+
+    /**
+     * Initializes a new cascading-capable menu popup.
+     *
+     * @param anchor A parent view to get the {@link android.view.View#getWindowToken()} token from.
+     */
+    public CascadingMenuPopup(@NonNull Context context, @NonNull View anchor,
+            @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes, boolean overflowOnly) {
+        mContext = context;
+        mAnchorView = anchor;
+        mPopupStyleAttr = popupStyleAttr;
+        mPopupStyleRes = popupStyleRes;
+        mOverflowOnly = overflowOnly;
+
+        mForceShowIcon = false;
+        mLastPosition = getInitialMenuPosition();
+
+        final Resources res = context.getResources();
+        mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+                res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+        mSubMenuHoverHandler = new Handler();
+    }
+
+    @Override
+    public void setForceShowIcon(boolean forceShow) {
+        mForceShowIcon = forceShow;
+    }
+
+    private MenuPopupWindow createPopupWindow() {
+        MenuPopupWindow popupWindow = new MenuPopupWindow(
+                mContext, null, mPopupStyleAttr, mPopupStyleRes);
+        popupWindow.setHoverListener(mMenuItemHoverListener);
+        popupWindow.setOnItemClickListener(this);
+        popupWindow.setOnDismissListener(this);
+        popupWindow.setAnchorView(mAnchorView);
+        popupWindow.setDropDownGravity(mDropDownGravity);
+        popupWindow.setModal(true);
+        return popupWindow;
+    }
+
+    @Override
+    public void show() {
+        if (isShowing()) {
+            return;
+        }
+
+        // Display all pending menus.
+        for (MenuBuilder menu : mPendingMenus) {
+            showMenu(menu);
+        }
+        mPendingMenus.clear();
+
+        mShownAnchorView = mAnchorView;
+
+        if (mShownAnchorView != null) {
+            final boolean addGlobalListener = mTreeObserver == null;
+            mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest
+            if (addGlobalListener) {
+                mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+            }
+        }
+    }
+
+    @Override
+    public void dismiss() {
+        // Need to make another list to avoid a concurrent modification
+        // exception, as #onDismiss may clear mPopupWindows while we are
+        // iterating. Remove from the last added menu so that the callbacks
+        // are received in order from foreground to background.
+        final int length = mShowingMenus.size();
+        if (length > 0) {
+            final CascadingMenuInfo[] addedMenus =
+                    mShowingMenus.toArray(new CascadingMenuInfo[length]);
+            for (int i = length - 1; i >= 0; i--) {
+                final CascadingMenuInfo info = addedMenus[i];
+                if (info.window.isShowing()) {
+                    info.window.dismiss();
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+            dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Determines the proper initial menu position for the current LTR/RTL configuration.
+     * @return The initial position.
+     */
+    @HorizPosition
+    private int getInitialMenuPosition() {
+        final int layoutDirection = ViewCompat.getLayoutDirection(mAnchorView);
+        return layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT :
+                HORIZ_POSITION_RIGHT;
+    }
+
+    /**
+     * Determines whether the next submenu (of the given width) should display on the right or on
+     * the left of the most recent menu.
+     *
+     * @param nextMenuWidth Width of the next submenu to display.
+     * @return The position to display it.
+     */
+    @HorizPosition
+    private int getNextMenuPosition(int nextMenuWidth) {
+        ListView lastListView = mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+
+        final int[] screenLocation = new int[2];
+        lastListView.getLocationOnScreen(screenLocation);
+
+        final Rect displayFrame = new Rect();
+        mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame);
+
+        if (mLastPosition == HORIZ_POSITION_RIGHT) {
+            final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth;
+            if (right > displayFrame.right) {
+                return HORIZ_POSITION_LEFT;
+            }
+            return HORIZ_POSITION_RIGHT;
+        } else { // LEFT
+            final int left = screenLocation[0] - nextMenuWidth;
+            if (left < 0) {
+                return HORIZ_POSITION_RIGHT;
+            }
+            return HORIZ_POSITION_LEFT;
+        }
+    }
+
+    @Override
+    public void addMenu(MenuBuilder menu) {
+        menu.addMenuPresenter(this, mContext);
+
+        if (isShowing()) {
+            showMenu(menu);
+        } else {
+            mPendingMenus.add(menu);
+        }
+    }
+
+    /**
+     * Prepares and shows the specified menu immediately.
+     *
+     * @param menu the menu to show
+     */
+    private void showMenu(@NonNull MenuBuilder menu) {
+        final LayoutInflater inflater = LayoutInflater.from(mContext);
+        final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+        adapter.setForceShowIcon(mForceShowIcon);
+
+        final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
+        final MenuPopupWindow popupWindow = createPopupWindow();
+        popupWindow.setAdapter(adapter);
+        popupWindow.setWidth(menuWidth);
+        popupWindow.setDropDownGravity(mDropDownGravity);
+
+        final CascadingMenuInfo parentInfo;
+        final View parentView;
+        if (mShowingMenus.size() > 0) {
+            parentInfo = mShowingMenus.get(mShowingMenus.size() - 1);
+            parentView = findParentViewForSubmenu(parentInfo, menu);
+        } else {
+            parentInfo = null;
+            parentView = null;
+        }
+
+        final int x;
+        final int y;
+        final Rect epicenterBounds;
+        if (parentView != null) {
+            // This menu is a cascading submenu anchored to a parent view.
+            popupWindow.setTouchModal(false);
+            popupWindow.setEnterTransition(null);
+
+            final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth);
+            final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
+            mLastPosition = nextMenuPosition;
+
+            final int[] tempLocation = new int[2];
+
+            // This popup menu will be positioned relative to the top-left edge
+            // of the view representing its parent menu.
+            parentView.getLocationInWindow(tempLocation);
+            final int parentOffsetLeft = parentInfo.window.getHorizontalOffset() + tempLocation[0];
+            final int parentOffsetTop = parentInfo.window.getVerticalOffset() + tempLocation[1];
+
+            // By now, mDropDownGravity is the resolved absolute gravity, so
+            // this should work in both LTR and RTL.
+            if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
+                if (showOnRight) {
+                    x = parentOffsetLeft + menuWidth;
+                } else {
+                    x = parentOffsetLeft - parentView.getWidth();
+                }
+            } else {
+                if (showOnRight) {
+                    x = parentOffsetLeft + parentView.getWidth();
+                } else {
+                    x = parentOffsetLeft - menuWidth;
+                }
+            }
+
+            y = parentOffsetTop;
+            epicenterBounds = null;
+        } else {
+            x = mInitXOffset;
+            y = mInitYOffset;
+            epicenterBounds = getEpicenterBounds();
+        }
+
+        popupWindow.setHorizontalOffset(x);
+        popupWindow.setVerticalOffset(y);
+        popupWindow.setEpicenterBounds(epicenterBounds);
+
+        final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition);
+        mShowingMenus.add(menuInfo);
+
+        popupWindow.show();
+
+        // If this is the root menu, show the title if requested.
+        if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) {
+            final ListView listView = popupWindow.getListView();
+            final FrameLayout titleItemView = (FrameLayout) inflater.inflate(
+                    R.layout.abc_popup_menu_header_item_layout, listView, false);
+            final TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+            titleItemView.setEnabled(false);
+            titleView.setText(menu.getHeaderTitle());
+            listView.addHeaderView(titleItemView, null, false);
+
+            // Show again to update the title.
+            popupWindow.show();
+        }
+    }
+
+    /**
+     * Returns the menu item within the specified parent menu that owns
+     * specified submenu.
+     *
+     * @param parent the parent menu
+     * @param submenu the submenu for which the index should be returned
+     * @return the menu item that owns the submenu, or {@code null} if not
+     *         present
+     */
+    private MenuItem findMenuItemForSubmenu(
+            @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) {
+        for (int i = 0, count = parent.size(); i < count; i++) {
+            final MenuItem item = parent.getItem(i);
+            if (item.hasSubMenu() && submenu == item.getSubMenu()) {
+                return item;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Attempts to find the view for the menu item that owns the specified
+     * submenu.
+     *
+     * @param parentInfo info for the parent menu
+     * @param submenu the submenu whose parent view should be obtained
+     * @return the parent view, or {@code null} if one could not be found
+     */
+    @Nullable
+    private View findParentViewForSubmenu(
+            @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) {
+        final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu);
+        if (owner == null) {
+            // Couldn't find the submenu owner.
+            return null;
+        }
+
+        // The adapter may be wrapped. Adjust the index if necessary.
+        final int headersCount;
+        final MenuAdapter menuAdapter;
+        final ListView listView = parentInfo.getListView();
+        final ListAdapter listAdapter = listView.getAdapter();
+        if (listAdapter instanceof HeaderViewListAdapter) {
+            final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter;
+            headersCount = headerAdapter.getHeadersCount();
+            menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+        } else {
+            headersCount = 0;
+            menuAdapter = (MenuAdapter) listAdapter;
+        }
+
+        // Find the index within the menu adapter's data set of the menu item.
+        int ownerPosition = AbsListView.INVALID_POSITION;
+        for (int i = 0, count = menuAdapter.getCount(); i < count; i++) {
+            if (owner == menuAdapter.getItem(i)) {
+                ownerPosition = i;
+                break;
+            }
+        }
+        if (ownerPosition == AbsListView.INVALID_POSITION) {
+            // Couldn't find the owner within the menu adapter.
+            return null;
+        }
+
+        // Adjust the index for the adapter used to display views.
+        ownerPosition += headersCount;
+
+        // Adjust the index for the visible views.
+        final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition();
+        if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) {
+            // Not visible on screen.
+            return null;
+        }
+
+        return listView.getChildAt(ownerViewPosition);
+    }
+
+    /**
+     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+     */
+    @Override
+    public boolean isShowing() {
+        return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing();
+    }
+
+    /**
+     * Called when one or more of the popup windows was dismissed.
+     */
+    @Override
+    public void onDismiss() {
+        // The dismiss listener doesn't pass the calling window, so walk
+        // through the stack to figure out which one was just dismissed.
+        CascadingMenuInfo dismissedInfo = null;
+        for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+            final CascadingMenuInfo info = mShowingMenus.get(i);
+            if (!info.window.isShowing()) {
+                dismissedInfo = info;
+                break;
+            }
+        }
+
+        // Close all menus starting from the dismissed menu, passing false
+        // since we are manually closing only a subset of windows.
+        if (dismissedInfo != null) {
+            dismissedInfo.menu.close(false);
+        }
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        for (CascadingMenuInfo info : mShowingMenus) {
+            toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged();
+        }
+    }
+
+    @Override
+    public void setCallback(Callback cb) {
+        mPresenterCallback = cb;
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        // Don't allow double-opening of the same submenu.
+        for (CascadingMenuInfo info : mShowingMenus) {
+            if (subMenu == info.menu) {
+                // Just re-focus that one.
+                info.getListView().requestFocus();
+                return true;
+            }
+        }
+
+        if (subMenu.hasVisibleItems()) {
+            addMenu(subMenu);
+
+            if (mPresenterCallback != null) {
+                mPresenterCallback.onOpenSubMenu(subMenu);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Finds the index of the specified menu within the list of added menus.
+     *
+     * @param menu the menu to find
+     * @return the index of the menu, or {@code -1} if not present
+     */
+    private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) {
+        for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+            final CascadingMenuInfo info  = mShowingMenus.get(i);
+            if (menu == info.menu) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        final int menuIndex = findIndexOfAddedMenu(menu);
+        if (menuIndex < 0) {
+            return;
+        }
+
+        // Recursively close descendant menus.
+        final int nextMenuIndex = menuIndex + 1;
+        if (nextMenuIndex < mShowingMenus.size()) {
+            final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex);
+            childInfo.menu.close(false /* closeAllMenus */);
+        }
+
+        // Close the target menu.
+        final CascadingMenuInfo info = mShowingMenus.remove(menuIndex);
+        info.menu.removeMenuPresenter(this);
+        if (mShouldCloseImmediately) {
+            // Disable all exit animations.
+            info.window.setExitTransition(null);
+            info.window.setAnimationStyle(0);
+        }
+        info.window.dismiss();
+
+        final int count = mShowingMenus.size();
+        if (count > 0) {
+            mLastPosition = mShowingMenus.get(count - 1).position;
+        } else {
+            mLastPosition = getInitialMenuPosition();
+        }
+
+        if (count == 0) {
+            // This was the last window. Clean up.
+            dismiss();
+
+            if (mPresenterCallback != null) {
+                mPresenterCallback.onCloseMenu(menu, true);
+            }
+
+            if (mTreeObserver != null) {
+                if (mTreeObserver.isAlive()) {
+                    mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+                }
+                mTreeObserver = null;
+            }
+
+
+            // If every [sub]menu was dismissed, that means the whole thing was
+            // dismissed, so notify the owner.
+            mOnDismissListener.onDismiss();
+        } else if (allMenusAreClosing) {
+            // Close all menus starting from the root. This will recursively
+            // close any remaining menus, so we don't need to propagate the
+            // "closeAllMenus" flag. The last window will clean up.
+            final CascadingMenuInfo rootInfo = mShowingMenus.get(0);
+            rootInfo.menu.close(false /* closeAllMenus */);
+        }
+    }
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        return null;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+    }
+
+    @Override
+    public void setGravity(int dropDownGravity) {
+        if (mRawDropDownGravity != dropDownGravity) {
+            mRawDropDownGravity = dropDownGravity;
+            mDropDownGravity = GravityCompat.getAbsoluteGravity(
+                    dropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+        }
+    }
+
+    @Override
+    public void setAnchorView(@NonNull View anchor) {
+        if (mAnchorView != anchor) {
+            mAnchorView = anchor;
+
+            // Gravity resolution may have changed, update from raw gravity.
+            mDropDownGravity = GravityCompat.getAbsoluteGravity(
+                    mRawDropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+        }
+    }
+
+    @Override
+    public void setOnDismissListener(OnDismissListener listener) {
+        mOnDismissListener = listener;
+    }
+
+    @Override
+    public ListView getListView() {
+        return mShowingMenus.isEmpty()
+                ? null
+                : mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+    }
+
+    @Override
+    public void setHorizontalOffset(int x) {
+        mInitXOffset = x;
+    }
+
+    @Override
+    public void setVerticalOffset(int y) {
+        mInitYOffset = y;
+    }
+
+    @Override
+    public void setShowTitle(boolean showTitle) {
+        mShowTitle = showTitle;
+    }
+
+    private static class CascadingMenuInfo {
+        public final MenuPopupWindow window;
+        public final MenuBuilder menu;
+        public final int position;
+
+        public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu,
+                int position) {
+            this.window = window;
+            this.menu = menu;
+            this.position = position;
+        }
+
+        public ListView getListView() {
+            return window.getListView();
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
index 388e78f..2d6def1 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
@@ -17,9 +17,9 @@
 package android.support.v7.view.menu;
 
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.support.v7.appcompat.R;
+import android.support.v7.widget.TintTypedArray;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -37,7 +37,6 @@
  * @hide
  */
 public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
-
     private static final String TAG = "ListMenuItemView";
     private MenuItemImpl mItemData;
 
@@ -46,25 +45,29 @@
     private TextView mTitleView;
     private CheckBox mCheckBox;
     private TextView mShortcutView;
+    private ImageView mSubMenuArrowView;
 
     private Drawable mBackground;
     private int mTextAppearance;
     private Context mTextAppearanceContext;
     private boolean mPreserveIconSpacing;
+    private Drawable mSubMenuArrow;
 
     private int mMenuType;
 
-    private Context mContext;
     private LayoutInflater mInflater;
 
     private boolean mForceShowIcon;
 
-    public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs);
-        mContext = context;
+    public ListMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.listMenuViewStyle);
+    }
 
-        final TypedArray a = context.obtainStyledAttributes(
-                attrs, R.styleable.MenuView, defStyle, 0);
+    public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs);
+
+        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(),
+                attrs, R.styleable.MenuView, defStyleAttr, 0);
 
         mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground);
         mTextAppearance = a.getResourceId(R.styleable.
@@ -72,14 +75,11 @@
         mPreserveIconSpacing = a.getBoolean(
                 R.styleable.MenuView_preserveIconSpacing, false);
         mTextAppearanceContext = context;
+        mSubMenuArrow = a.getDrawable(R.styleable.MenuView_subMenuArrow);
 
         a.recycle();
     }
 
-    public ListMenuItemView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -93,6 +93,10 @@
         }
 
         mShortcutView = (TextView) findViewById(R.id.shortcut);
+        mSubMenuArrowView = (ImageView) findViewById(R.id.submenuarrow);
+        if (mSubMenuArrowView != null) {
+            mSubMenuArrowView.setImageDrawable(mSubMenuArrow);
+        }
     }
 
     public void initialize(MenuItemImpl itemData, int menuType) {
@@ -106,6 +110,7 @@
         setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
         setIcon(itemData.getIcon());
         setEnabled(itemData.isEnabled());
+        setSubMenuArrowVisible(itemData.hasSubMenu());
     }
 
     public void setForceShowIcon(boolean forceShow) {
@@ -190,6 +195,12 @@
         compoundButton.setChecked(checked);
     }
 
+    private void setSubMenuArrowVisible(boolean hasSubmenu) {
+        if (mSubMenuArrowView != null) {
+            mSubMenuArrowView.setVisibility(hasSubmenu ? View.VISIBLE : View.GONE);
+        }
+    }
+
     public void setShortcut(boolean showShortcut, char shortcutKey) {
         final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
                 ? VISIBLE : GONE;
@@ -274,7 +285,7 @@
 
     private LayoutInflater getInflater() {
         if (mInflater == null) {
-            mInflater = LayoutInflater.from(mContext);
+            mInflater = LayoutInflater.from(getContext());
         }
         return mInflater;
     }
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
new file mode 100644
index 0000000..08ec36d
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.view.menu;
+
+import android.support.v7.appcompat.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class MenuAdapter extends BaseAdapter {
+    static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+
+    MenuBuilder mAdapterMenu;
+
+    private int mExpandedIndex = -1;
+
+    private boolean mForceShowIcon;
+    private final boolean mOverflowOnly;
+    private final LayoutInflater mInflater;
+
+    public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
+        mOverflowOnly = overflowOnly;
+        mInflater = inflater;
+        mAdapterMenu = menu;
+        findExpandedIndex();
+    }
+
+    public boolean getForceShowIcon() {
+        return mForceShowIcon;
+    }
+
+    public void setForceShowIcon(boolean forceShow) {
+        mForceShowIcon = forceShow;
+    }
+
+    public int getCount() {
+        ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+        if (mExpandedIndex < 0) {
+            return items.size();
+        }
+        return items.size() - 1;
+    }
+
+    public MenuBuilder getAdapterMenu() {
+        return mAdapterMenu;
+    }
+
+    public MenuItemImpl getItem(int position) {
+        ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+        if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+            position++;
+        }
+        return items.get(position);
+    }
+
+    public long getItemId(int position) {
+        // Since a menu item's ID is optional, we'll use the position as an
+        // ID for the item in the AdapterView
+        return position;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+        }
+
+        MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+        if (mForceShowIcon) {
+            ((ListMenuItemView) convertView).setForceShowIcon(true);
+        }
+        itemView.initialize(getItem(position), 0);
+        return convertView;
+    }
+
+    void findExpandedIndex() {
+        final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
+        if (expandedItem != null) {
+            final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems();
+            final int count = items.size();
+            for (int i = 0; i < count; i++) {
+                final MenuItemImpl item = items.get(i);
+                if (item == expandedItem) {
+                    mExpandedIndex = i;
+                    return;
+                }
+            }
+        }
+        mExpandedIndex = -1;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        findExpandedIndex();
+        super.notifyDataSetChanged();
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
index b976655..f163215 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
@@ -26,6 +26,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.internal.view.SupportMenu;
 import android.support.v4.internal.view.SupportMenuItem;
@@ -69,8 +70,8 @@
     };
 
     private final Context mContext;
-
     private final Resources mResources;
+    private final boolean mShowCascadingMenus;
 
     /**
      * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode() instead of accessing
@@ -214,14 +215,16 @@
     public MenuBuilder(Context context) {
         mContext = context;
         mResources = context.getResources();
+        mShowCascadingMenus = context.getResources().getBoolean(
+                R.bool.abc_config_enableCascadingSubmenus);
 
-        mItems = new ArrayList<MenuItemImpl>();
+        mItems = new ArrayList<>();
 
-        mVisibleItems = new ArrayList<MenuItemImpl>();
+        mVisibleItems = new ArrayList<>();
         mIsVisibleItemsStale = true;
 
-        mActionItems = new ArrayList<MenuItemImpl>();
-        mNonActionItems = new ArrayList<MenuItemImpl>();
+        mActionItems = new ArrayList<>();
+        mNonActionItems = new ArrayList<>();
         mIsActionItemsStale = true;
 
         setShortcutsVisibleInner(true);
@@ -842,7 +845,7 @@
         }
 
         if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
-            close(true);
+            close(true /* closeAllMenus */);
         }
 
         return handled;
@@ -961,9 +964,13 @@
         final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
         if (itemImpl.hasCollapsibleActionView()) {
             invoked |= itemImpl.expandActionView();
-            if (invoked) close(true);
+            if (invoked) {
+                close(true /* closeAllMenus */);
+            }
         } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
-            close(false);
+            if (!mShowCascadingMenus) {
+                close(false /* closeAllMenus */);
+            }
 
             if (!itemImpl.hasSubMenu()) {
                 itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
@@ -974,10 +981,12 @@
                 provider.onPrepareSubMenu(subMenu);
             }
             invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
-            if (!invoked) close(true);
+            if (!invoked) {
+                close(true /* closeAllMenus */);
+            }
         } else {
             if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
-                close(true);
+                close(true /* closeAllMenus */);
             }
         }
 
@@ -985,15 +994,14 @@
     }
 
     /**
-     * Closes the visible menu.
+     * Closes the menu.
      *
-     * @param allMenusAreClosing Whether the menus are completely closing (true),
-     *            or whether there is another menu coming in this menu's place
-     *            (false). For example, if the menu is closing because a
-     *            sub menu is about to be shown, <var>allMenusAreClosing</var>
-     *            is false.
+     * @param closeAllMenus {@code true} if all displayed menus and submenus
+     *                      should be completely closed (as when a menu item is
+     *                      selected) or {@code false} if only this menu should
+     *                      be closed
      */
-    public final void close(boolean allMenusAreClosing) {
+    public final void close(boolean closeAllMenus) {
         if (mIsClosing) return;
 
         mIsClosing = true;
@@ -1002,7 +1010,7 @@
             if (presenter == null) {
                 mPresenters.remove(ref);
             } else {
-                presenter.onCloseMenu(this, allMenusAreClosing);
+                presenter.onCloseMenu(this, closeAllMenus);
             }
         }
         mIsClosing = false;
@@ -1010,7 +1018,7 @@
 
     @Override
     public void close() {
-        close(true);
+        close(true /* closeAllMenus */);
     }
 
     /**
@@ -1076,6 +1084,7 @@
         onItemsChanged(true);
     }
 
+    @NonNull
     public ArrayList<MenuItemImpl> getVisibleItems() {
         if (!mIsVisibleItemsStale) return mVisibleItems;
 
@@ -1284,7 +1293,6 @@
 
     /**
      * Gets the root menu (if this is a submenu, find its root menu).
-     *
      * @return The root menu.
      */
     public MenuBuilder getRootMenu() {
@@ -1302,7 +1310,7 @@
         mCurrentMenuInfo = menuInfo;
     }
 
-    void setOptionalIconsVisible(boolean visible) {
+    public void setOptionalIconsVisible(boolean visible) {
         mOptionalIconsVisible = visible;
     }
 
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
similarity index 69%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
index 8126b38..b861643 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
-
-import android.app.Activity;
+package android.support.v7.view.menu;
 
 /**
- * @hide
+ * Interface for a helper capable of presenting a menu.
  */
-public class GridLayoutTestActivity extends Activity {
-}
+interface MenuHelper {
+    void setPresenterCallback(MenuPresenter.Callback cb);
+    void dismiss();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
new file mode 100644
index 0000000..0dbf5cf
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
@@ -0,0 +1,184 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+/**
+ * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window
+ * environment.
+ */
+abstract class MenuPopup implements ShowableListMenu, MenuPresenter,
+        AdapterView.OnItemClickListener {
+    private Rect mEpicenterBounds;
+
+    public abstract void setForceShowIcon(boolean forceShow);
+
+    /**
+     * Adds the given menu to the popup, if it is capable of displaying submenus within itself.
+     * If menu is the first menu shown, it won't be displayed until show() is called.
+     * If the popup was already showing, adding a submenu via this method will cause that new
+     * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of
+     * showing its own submenus).
+     *
+     * @param menu
+     */
+    public abstract void addMenu(MenuBuilder menu);
+
+    public abstract void setGravity(int dropDownGravity);
+
+    public abstract void setAnchorView(View anchor);
+
+    public abstract void setHorizontalOffset(int x);
+
+    public abstract void setVerticalOffset(int y);
+
+    /**
+     * Specifies the anchor-relative bounds of the popup's transition
+     * epicenter.
+     *
+     * @param bounds anchor-relative bounds
+     */
+    public void setEpicenterBounds(Rect bounds) {
+        mEpicenterBounds = bounds;
+    }
+
+    /**
+     * @return anchor-relative bounds of the popup's transition epicenter
+     */
+    public Rect getEpicenterBounds() {
+        return mEpicenterBounds;
+    }
+
+    /**
+     * Set whether a title entry should be shown in the popup menu (if a title exists for the
+     * menu).
+     *
+     * @param showTitle
+     */
+    public abstract void setShowTitle(boolean showTitle);
+
+    /**
+     * Set a listener to receive a callback when the popup is dismissed.
+     *
+     * @param listener Listener that will be notified when the popup is dismissed.
+     */
+    public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener);
+
+    @Override
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
+        // Don't need to do anything; we added as a presenter in the constructor.
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        throw new UnsupportedOperationException("MenuPopups manage their own views");
+    }
+
+    @Override
+    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+        return false;
+    }
+
+    @Override
+    public int getId() {
+        return 0;
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        ListAdapter outerAdapter = (ListAdapter) parent.getAdapter();
+        MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter);
+
+        // Use the position from the outer adapter so that if a header view was added, we don't get
+        // an off-by-1 error in position.
+        wrappedAdapter.mAdapterMenu.performItemAction((MenuItem) outerAdapter.getItem(position), 0);
+    }
+
+    /**
+     * Measures the width of the given menu view.
+     *
+     * @param view The view to measure.
+     * @return The width.
+     */
+    protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent,
+            Context context, int maxAllowedWidth) {
+        // Menus don't tend to be long, so this is more sane than it looks.
+        int maxWidth = 0;
+        View itemView = null;
+        int itemType = 0;
+
+        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int count = adapter.getCount();
+        for (int i = 0; i < count; i++) {
+            final int positionType = adapter.getItemViewType(i);
+            if (positionType != itemType) {
+                itemType = positionType;
+                itemView = null;
+            }
+
+            if (parent == null) {
+                parent = new FrameLayout(context);
+            }
+
+            itemView = adapter.getView(i, itemView, parent);
+            itemView.measure(widthMeasureSpec, heightMeasureSpec);
+
+            final int itemWidth = itemView.getMeasuredWidth();
+            if (itemWidth >= maxAllowedWidth) {
+                return maxAllowedWidth;
+            } else if (itemWidth > maxWidth) {
+                maxWidth = itemWidth;
+            }
+        }
+
+        return maxWidth;
+    }
+
+    /**
+     * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for
+     * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could
+     * happen if a header view was added on the menu.)
+     *
+     * @param adapter
+     * @return
+     */
+    protected static MenuAdapter toMenuAdapter(ListAdapter adapter) {
+        if (adapter instanceof HeaderViewListAdapter) {
+            return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter();
+        }
+        return (MenuAdapter) adapter;
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
index c0e0fb5..4a912d2 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
@@ -17,110 +17,111 @@
 package android.support.v7.view.menu;
 
 import android.content.Context;
-import android.content.res.Resources;
-import android.os.Parcelable;
+import android.graphics.Rect;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
-import android.support.v7.widget.ListPopupWindow;
 import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListAdapter;
-import android.widget.PopupWindow;
-
-import java.util.ArrayList;
+import android.widget.PopupWindow.OnDismissListener;
 
 /**
  * Presents a menu as a small, simple popup anchored to another view.
  *
  * @hide
  */
-public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
-        ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
-        MenuPresenter {
-
-    private static final String TAG = "MenuPopupHelper";
-
-    static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+public class MenuPopupHelper implements MenuHelper {
+    private static final int TOUCH_EPICENTER_SIZE_DP = 48;
 
     private final Context mContext;
-    private final LayoutInflater mInflater;
+
+    // Immutable cached popup menu properties.
     private final MenuBuilder mMenu;
-    private final MenuAdapter mAdapter;
     private final boolean mOverflowOnly;
-    private final int mPopupMaxWidth;
     private final int mPopupStyleAttr;
     private final int mPopupStyleRes;
 
+    // Mutable cached popup menu properties.
     private View mAnchorView;
-    private ListPopupWindow mPopup;
-    private ViewTreeObserver mTreeObserver;
-    private Callback mPresenterCallback;
+    private int mDropDownGravity = Gravity.START;
+    private boolean mForceShowIcon;
+    private MenuPresenter.Callback mPresenterCallback;
 
-    boolean mForceShowIcon;
+    private MenuPopup mPopup;
+    private OnDismissListener mOnDismissListener;
 
-    private ViewGroup mMeasureParent;
-
-    /** Whether the cached content width value is valid. */
-    private boolean mHasContentWidth;
-
-    /** Cached content width from {@link #measureContentWidth}. */
-    private int mContentWidth;
-
-    private int mDropDownGravity = Gravity.NO_GRAVITY;
-
-    public MenuPopupHelper(Context context, MenuBuilder menu) {
-        this(context, menu, null, false, R.attr.popupMenuStyle);
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
+        this(context, menu, null, false, R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
-        this(context, menu, anchorView, false, R.attr.popupMenuStyle);
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView) {
+        this(context, menu, anchorView, false, R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView,
+            boolean overflowOnly, @AttrRes int popupStyleAttr) {
         this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+            @StyleRes int popupStyleRes) {
         mContext = context;
-        mInflater = LayoutInflater.from(context);
         mMenu = menu;
-        mAdapter = new MenuAdapter(mMenu);
+        mAnchorView = anchorView;
         mOverflowOnly = overflowOnly;
         mPopupStyleAttr = popupStyleAttr;
         mPopupStyleRes = popupStyleRes;
-
-        final Resources res = context.getResources();
-        mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
-                res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
-
-        mAnchorView = anchorView;
-
-        // Present the menu using our context, not the menu builder's context.
-        menu.addMenuPresenter(this, context);
     }
 
-    public void setAnchorView(View anchor) {
+    public void setOnDismissListener(@Nullable OnDismissListener listener) {
+        mOnDismissListener = listener;
+    }
+
+    /**
+      * Sets the view to which the popup window is anchored.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param anchor the view to which the popup window should be anchored
+      */
+    public void setAnchorView(@NonNull View anchor) {
         mAnchorView = anchor;
     }
 
-    public void setForceShowIcon(boolean forceShow) {
-        mForceShowIcon = forceShow;
+    /**
+     * Sets whether the popup menu's adapter is forced to show icons in the
+     * menu item views.
+     * <p>
+     * Changes take effect on the next call to show().
+     *
+     * @param forceShowIcon {@code true} to force icons to be shown, or
+     *                  {@code false} for icons to be optionally shown
+     */
+    public void setForceShowIcon(boolean forceShowIcon) {
+        mForceShowIcon = forceShowIcon;
     }
 
+    /**
+      * Sets the alignment of the popup window relative to the anchor view.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param gravity alignment of the popup relative to the anchor
+      */
     public void setGravity(int gravity) {
         mDropDownGravity = gravity;
     }
 
+    /**
+     * @return alignment of the popup relative to the anchor
+     */
     public int getGravity() {
         return mDropDownGravity;
     }
@@ -131,53 +132,161 @@
         }
     }
 
-    public ListPopupWindow getPopup() {
+    public void show(int x, int y) {
+        if (!tryShow(x, y)) {
+            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+        }
+    }
+
+    @NonNull
+    public MenuPopup getPopup() {
+        if (mPopup == null) {
+            mPopup = createPopup();
+        }
         return mPopup;
     }
 
+    /**
+     * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
+     *
+     * @return {@code true} if the popup was shown or was already showing prior to calling this
+     *         method, {@code false} otherwise
+     */
     public boolean tryShow() {
-        mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
-        mPopup.setOnDismissListener(this);
-        mPopup.setOnItemClickListener(this);
-        mPopup.setAdapter(mAdapter);
-        mPopup.setModal(true);
+        if (isShowing()) {
+            return true;
+        }
 
-        View anchor = mAnchorView;
-        if (anchor != null) {
-            final boolean addGlobalListener = mTreeObserver == null;
-            mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
-            if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
-            mPopup.setAnchorView(anchor);
-            mPopup.setDropDownGravity(mDropDownGravity);
-        } else {
+        if (mAnchorView == null) {
             return false;
         }
 
-        if (!mHasContentWidth) {
-            mContentWidth = measureContentWidth();
-            mHasContentWidth = true;
-        }
-
-        mPopup.setContentWidth(mContentWidth);
-        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-        mPopup.show();
-        mPopup.getListView().setOnKeyListener(this);
+        showPopup(0, 0, false, false);
         return true;
     }
 
+    /**
+     * Shows the popup menu and makes a best-effort to anchor it to the
+     * specified (x,y) coordinate relative to the anchor view.
+     * <p>
+     * Additionally, the popup's transition epicenter (see
+     * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
+     * centered on the specified coordinate, rather than using the bounds of
+     * the anchor view.
+     * <p>
+     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+     * display the popup with its top-left corner at (x,y) relative to the
+     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+     * popup's top-right corner will be at (x,y).
+     * <p>
+     * If the popup cannot be displayed fully on-screen, this method will
+     * attempt to scroll the anchor view's ancestors and/or offset the popup
+     * such that it may be displayed fully on-screen.
+     *
+     * @param x x coordinate relative to the anchor view
+     * @param y y coordinate relative to the anchor view
+     * @return {@code true} if the popup was shown or was already showing prior
+     *         to calling this method, {@code false} otherwise
+     */
+    public boolean tryShow(int x, int y) {
+        if (isShowing()) {
+            return true;
+        }
+
+        if (mAnchorView == null) {
+            return false;
+        }
+
+        showPopup(x, y, true, true);
+        return true;
+    }
+
+    /**
+     * Creates the popup and assigns cached properties.
+     *
+     * @return an initialized popup
+     */
+    @NonNull
+    private MenuPopup createPopup() {
+        final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+                R.bool.abc_config_enableCascadingSubmenus);
+
+        final MenuPopup popup;
+        if (enableCascadingSubmenus) {
+            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        } else {
+            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        }
+
+        // Assign immutable properties.
+        popup.addMenu(mMenu);
+        popup.setOnDismissListener(mInternalOnDismissListener);
+
+        // Assign mutable properties. These may be reassigned later.
+        popup.setAnchorView(mAnchorView);
+        popup.setCallback(mPresenterCallback);
+        popup.setForceShowIcon(mForceShowIcon);
+        popup.setGravity(mDropDownGravity);
+
+        return popup;
+    }
+
+    private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
+        final MenuPopup popup = getPopup();
+        popup.setShowTitle(showTitle);
+
+        if (useOffsets) {
+            // If the resolved drop-down gravity is RIGHT, the popup's right
+            // edge will be aligned with the anchor view. Adjust by the anchor
+            // width such that the top-right corner is at the X offset.
+            final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
+                    ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+                xOffset -= mAnchorView.getWidth();
+            }
+
+            popup.setHorizontalOffset(xOffset);
+            popup.setVerticalOffset(yOffset);
+
+            // Set the transition epicenter to be roughly finger (or mouse
+            // cursor) sized and centered around the offset position. This
+            // will give the appearance that the window is emerging from
+            // the touch point.
+            final float density = mContext.getResources().getDisplayMetrics().density;
+            final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
+            final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
+                    xOffset + halfSize, yOffset + halfSize);
+            popup.setEpicenterBounds(epicenter);
+        }
+
+        popup.show();
+    }
+
+    /**
+     * Dismisses the popup, if showing.
+     */
+    @Override
     public void dismiss() {
         if (isShowing()) {
             mPopup.dismiss();
         }
     }
 
-    public void onDismiss() {
+    /**
+     * Called after the popup has been dismissed.
+     * <p>
+     * <strong>Note:</strong> Subclasses should call the super implementation
+     * last to ensure that any necessary tear down has occurred before the
+     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+     * is called.
+     */
+    protected void onDismiss() {
         mPopup = null;
-        mMenu.close();
-        if (mTreeObserver != null) {
-            if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
-            mTreeObserver.removeGlobalOnLayoutListener(this);
-            mTreeObserver = null;
+
+        if (mOnDismissListener != null) {
+            mOnDismissListener.onDismiss();
         }
     }
 
@@ -186,223 +295,20 @@
     }
 
     @Override
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        MenuAdapter adapter = mAdapter;
-        adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
-    }
-
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
-            dismiss();
-            return true;
-        }
-        return false;
-    }
-
-    private int measureContentWidth() {
-        // Menus don't tend to be long, so this is more sane than it looks.
-        int maxWidth = 0;
-        View itemView = null;
-        int itemType = 0;
-
-        final ListAdapter adapter = mAdapter;
-        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final int count = adapter.getCount();
-        for (int i = 0; i < count; i++) {
-            final int positionType = adapter.getItemViewType(i);
-            if (positionType != itemType) {
-                itemType = positionType;
-                itemView = null;
-            }
-
-            if (mMeasureParent == null) {
-                mMeasureParent = new FrameLayout(mContext);
-            }
-
-            itemView = adapter.getView(i, itemView, mMeasureParent);
-            itemView.measure(widthMeasureSpec, heightMeasureSpec);
-
-            final int itemWidth = itemView.getMeasuredWidth();
-            if (itemWidth >= mPopupMaxWidth) {
-                return mPopupMaxWidth;
-            } else if (itemWidth > maxWidth) {
-                maxWidth = itemWidth;
-            }
-        }
-
-        return maxWidth;
-    }
-
-    @Override
-    public void onGlobalLayout() {
-        if (isShowing()) {
-            final View anchor = mAnchorView;
-            if (anchor == null || !anchor.isShown()) {
-                dismiss();
-            } else if (isShowing()) {
-                // Recompute window size and position
-                mPopup.show();
-            }
-        }
-    }
-
-    @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
-        // Don't need to do anything; we added as a presenter in the constructor.
-    }
-
-    @Override
-    public MenuView getMenuView(ViewGroup root) {
-        throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
-    }
-
-    @Override
-    public void updateMenuView(boolean cleared) {
-        mHasContentWidth = false;
-
-        if (mAdapter != null) {
-            mAdapter.notifyDataSetChanged();
-        }
-    }
-
-    @Override
-    public void setCallback(Callback cb) {
+    public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
         mPresenterCallback = cb;
-    }
-
-    @Override
-    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
-        if (subMenu.hasVisibleItems()) {
-            MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView);
-            subPopup.setCallback(mPresenterCallback);
-
-            boolean preserveIconSpacing = false;
-            final int count = subMenu.size();
-            for (int i = 0; i < count; i++) {
-                MenuItem childItem = subMenu.getItem(i);
-                if (childItem.isVisible() && childItem.getIcon() != null) {
-                    preserveIconSpacing = true;
-                    break;
-                }
-            }
-            subPopup.setForceShowIcon(preserveIconSpacing);
-
-            if (subPopup.tryShow()) {
-                if (mPresenterCallback != null) {
-                    mPresenterCallback.onOpenSubMenu(subMenu);
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        // Only care about the (sub)menu we're presenting.
-        if (menu != mMenu) return;
-
-        dismiss();
-        if (mPresenterCallback != null) {
-            mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+        if (mPopup != null) {
+            mPopup.setCallback(cb);
         }
     }
 
-    @Override
-    public boolean flagActionItems() {
-        return false;
-    }
-
-    public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
-        return false;
-    }
-
-    public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
-        return false;
-    }
-
-    @Override
-    public int getId() {
-        return 0;
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        return null;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-    }
-
-    private class MenuAdapter extends BaseAdapter {
-        private MenuBuilder mAdapterMenu;
-        private int mExpandedIndex = -1;
-
-        public MenuAdapter(MenuBuilder menu) {
-            mAdapterMenu = menu;
-            findExpandedIndex();
-        }
-
-        public int getCount() {
-            ArrayList<MenuItemImpl> items = mOverflowOnly ?
-                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
-            if (mExpandedIndex < 0) {
-                return items.size();
-            }
-            return items.size() - 1;
-        }
-
-        public MenuItemImpl getItem(int position) {
-            ArrayList<MenuItemImpl> items = mOverflowOnly ?
-                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
-            if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
-                position++;
-            }
-            return items.get(position);
-        }
-
-        public long getItemId(int position) {
-            // Since a menu item's ID is optional, we'll use the position as an
-            // ID for the item in the AdapterView
-            return position;
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
-            }
-
-            MenuView.ItemView itemView = (MenuView.ItemView) convertView;
-            if (mForceShowIcon) {
-                ((ListMenuItemView) convertView).setForceShowIcon(true);
-            }
-            itemView.initialize(getItem(position), 0);
-            return convertView;
-        }
-
-        void findExpandedIndex() {
-            final MenuItemImpl expandedItem = mMenu.getExpandedItem();
-            if (expandedItem != null) {
-                final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
-                final int count = items.size();
-                for (int i = 0; i < count; i++) {
-                    final MenuItemImpl item = items.get(i);
-                    if (item == expandedItem) {
-                        mExpandedIndex = i;
-                        return;
-                    }
-                }
-            }
-            mExpandedIndex = -1;
-        }
-
+    /**
+     * Listener used to proxy dismiss callbacks to the helper's owner.
+     */
+    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
         @Override
-        public void notifyDataSetChanged() {
-            findExpandedIndex();
-            super.notifyDataSetChanged();
+        public void onDismiss() {
+            MenuPopupHelper.this.onDismiss();
         }
-    }
+    };
 }
-
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
similarity index 60%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
index 8126b38..addebd7 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
@@ -14,12 +14,24 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
+package android.support.v7.view.menu;
 
-import android.app.Activity;
+import android.widget.ListView;
 
 /**
+ * A list menu which can be shown and hidden and which is internally represented by a ListView.
+ *
  * @hide
  */
-public class GridLayoutTestActivity extends Activity {
-}
+public interface ShowableListMenu {
+    public void show();
+
+    public void dismiss();
+
+    public boolean isShowing();
+
+    /**
+     * @return The internal ListView for the visible menu.
+     */
+    public ListView getListView();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
new file mode 100644
index 0000000..2396651
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
@@ -0,0 +1,320 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Parcelable;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+/**
+ * A standard menu popup in which when a submenu is opened, it replaces its parent menu in the
+ * viewport.
+ */
+final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener,
+        MenuPresenter, OnKeyListener {
+
+    private final Context mContext;
+
+    private final MenuBuilder mMenu;
+    private final MenuAdapter mAdapter;
+    private final boolean mOverflowOnly;
+    private final int mPopupMaxWidth;
+    private final int mPopupStyleAttr;
+    private final int mPopupStyleRes;
+    // The popup window is final in order to couple its lifecycle to the lifecycle of the
+    // StandardMenuPopup.
+    private final MenuPopupWindow mPopup;
+
+    private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+        @Override
+        public void onGlobalLayout() {
+            if (isShowing()) {
+                final View anchor = mShownAnchorView;
+                if (anchor == null || !anchor.isShown()) {
+                    dismiss();
+                } else if (isShowing()) {
+                    // Recompute window size and position
+                    mPopup.show();
+                }
+            }
+        }
+    };
+
+    private PopupWindow.OnDismissListener mOnDismissListener;
+
+    private View mAnchorView;
+    private View mShownAnchorView;
+    private Callback mPresenterCallback;
+    private ViewTreeObserver mTreeObserver;
+
+    /** Whether the popup has been dismissed. Once dismissed, it cannot be opened again. */
+    private boolean mWasDismissed;
+
+    /** Whether the cached content width value is valid. */
+    private boolean mHasContentWidth;
+
+    /** Cached content width. */
+    private int mContentWidth;
+
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+
+    private int mXOffset;
+    private int mYOffset;
+    private boolean mShowTitle;
+
+    public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
+            int popupStyleRes, boolean overflowOnly) {
+        mContext = context;
+        mMenu = menu;
+        mOverflowOnly = overflowOnly;
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+        mPopupStyleAttr = popupStyleAttr;
+        mPopupStyleRes = popupStyleRes;
+
+        final Resources res = context.getResources();
+        mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+                res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+        mAnchorView = anchorView;
+
+        mPopup = new MenuPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
+
+        // Present the menu using our context, not the menu builder's context.
+        menu.addMenuPresenter(this, context);
+    }
+
+    @Override
+    public void setForceShowIcon(boolean forceShow) {
+        mAdapter.setForceShowIcon(forceShow);
+    }
+
+    @Override
+    public void setGravity(int gravity) {
+        mDropDownGravity = gravity;
+    }
+
+    private boolean tryShow() {
+        if (isShowing()) {
+            return true;
+        }
+
+        if (mWasDismissed || mAnchorView == null) {
+            return false;
+        }
+
+        mShownAnchorView = mAnchorView;
+
+        mPopup.setOnDismissListener(this);
+        mPopup.setOnItemClickListener(this);
+        mPopup.setModal(true);
+
+        final View anchor = mShownAnchorView;
+        final boolean addGlobalListener = mTreeObserver == null;
+        mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
+        if (addGlobalListener) {
+            mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+        }
+        mPopup.setAnchorView(anchor);
+        mPopup.setDropDownGravity(mDropDownGravity);
+
+        if (!mHasContentWidth) {
+            mContentWidth = measureIndividualMenuWidth(mAdapter, null, mContext, mPopupMaxWidth);
+            mHasContentWidth = true;
+        }
+
+        mPopup.setContentWidth(mContentWidth);
+        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+        mPopup.setHorizontalOffset(mXOffset);
+        mPopup.setVerticalOffset(mYOffset);
+        mPopup.setEpicenterBounds(getEpicenterBounds());
+        mPopup.show();
+
+        final ListView listView = mPopup.getListView();
+        listView.setOnKeyListener(this);
+
+        if (mShowTitle && mMenu.getHeaderTitle() != null) {
+            FrameLayout titleItemView =
+                    (FrameLayout) LayoutInflater.from(mContext).inflate(
+                            R.layout.abc_popup_menu_header_item_layout, listView, false);
+            TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+            if (titleView != null) {
+                titleView.setText(mMenu.getHeaderTitle());
+            }
+            titleItemView.setEnabled(false);
+            listView.addHeaderView(titleItemView, null, false);
+        }
+
+        // Since addHeaderView() needs to be called before setAdapter() pre-v14, we have to set the
+        // adapter as late as possible, and then call show again to update
+        mPopup.setAdapter(mAdapter);
+        mPopup.show();
+
+        return true;
+    }
+
+    @Override
+    public void show() {
+        if (!tryShow()) {
+            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+        }
+    }
+
+    @Override
+    public void dismiss() {
+        if (isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
+    @Override
+    public void addMenu(MenuBuilder menu) {
+        // No-op: standard implementation has only one menu which is set in the constructor.
+    }
+
+    @Override
+    public boolean isShowing() {
+        return !mWasDismissed && mPopup.isShowing();
+    }
+
+    @Override
+    public void onDismiss() {
+        mWasDismissed = true;
+        mMenu.close();
+
+        if (mTreeObserver != null) {
+            if (!mTreeObserver.isAlive()) mTreeObserver = mShownAnchorView.getViewTreeObserver();
+            mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+            mTreeObserver = null;
+        }
+        mOnDismissListener.onDismiss();
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        mHasContentWidth = false;
+
+        if (mAdapter != null) {
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    @Override
+    public void setCallback(Callback cb) {
+        mPresenterCallback = cb;
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (subMenu.hasVisibleItems()) {
+            final MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu,
+                    mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
+            subPopup.setPresenterCallback(mPresenterCallback);
+            subPopup.setForceShowIcon(mAdapter.getForceShowIcon());
+
+            // Show the new sub-menu popup at the same location as this popup.
+            if (subPopup.tryShow(mXOffset, mYOffset)) {
+                if (mPresenterCallback != null) {
+                    mPresenterCallback.onOpenSubMenu(subMenu);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        // Only care about the (sub)menu we're presenting.
+        if (menu != mMenu) return;
+
+        dismiss();
+        if (mPresenterCallback != null) {
+            mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+        }
+    }
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        return null;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+    }
+
+    @Override
+    public void setAnchorView(View anchor) {
+        mAnchorView = anchor;
+    }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+            dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setOnDismissListener(OnDismissListener listener) {
+        mOnDismissListener = listener;
+    }
+
+    @Override
+    public ListView getListView() {
+        return mPopup.getListView();
+    }
+
+
+    @Override
+    public void setHorizontalOffset(int x) {
+        mXOffset = x;
+    }
+
+    @Override
+    public void setVerticalOffset(int y) {
+        mYOffset = y;
+    }
+
+    @Override
+    public void setShowTitle(boolean showTitle) {
+        mShowTitle = showTitle;
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
index 687e52e..c3d9893 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
@@ -22,6 +22,8 @@
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.view.ActionProvider;
 import android.support.v4.view.GravityCompat;
@@ -34,6 +36,7 @@
 import android.support.v7.view.menu.MenuItemImpl;
 import android.support.v7.view.menu.MenuPopupHelper;
 import android.support.v7.view.menu.MenuView;
+import android.support.v7.view.menu.ShowableListMenu;
 import android.support.v7.view.menu.SubMenuBuilder;
 import android.util.SparseBooleanArray;
 import android.view.MenuItem;
@@ -86,7 +89,7 @@
     }
 
     @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
+    public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
         super.initForMenu(context, menu);
 
         final Resources res = context.getResources();
@@ -132,8 +135,7 @@
 
     public void onConfigurationChanged(Configuration newConfig) {
         if (!mMaxItemsSet) {
-            mMaxItems = mContext.getResources().getInteger(
-                    R.integer.abc_max_action_buttons);
+            mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
         }
         if (mMenu != null) {
             mMenu.onItemsChanged(true);
@@ -180,8 +182,11 @@
 
     @Override
     public MenuView getMenuView(ViewGroup root) {
+        MenuView oldMenuView = mMenuView;
         MenuView result = super.getMenuView(root);
-        ((ActionMenuView) result).setPresenter(this);
+        if (oldMenuView != result) {
+            ((ActionMenuView) result).setPresenter(this);
+        }
         return result;
     }
 
@@ -288,14 +293,29 @@
         }
         View anchor = findViewForItem(topSubMenu.getItem());
         if (anchor == null) {
-            if (mOverflowButton == null) return false;
-            anchor = mOverflowButton;
+            // This means the submenu was opened from an overflow menu item, indicating the
+            // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
+            // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
+            // responsibility to display the new submenu.
+            return false;
         }
 
         mOpenSubMenuId = subMenu.getItem().getItemId();
-        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
-        mActionButtonPopup.setAnchorView(anchor);
+
+        boolean preserveIconSpacing = false;
+        final int count = subMenu.size();
+        for (int i = 0; i < count; i++) {
+            MenuItem childItem = subMenu.getItem(i);
+            if (childItem.isVisible() && childItem.getIcon() != null) {
+                preserveIconSpacing = true;
+                break;
+            }
+        }
+
+        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
+        mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
         mActionButtonPopup.show();
+
         super.onSubMenuSelected(subMenu);
         return true;
     }
@@ -398,8 +418,16 @@
     }
 
     public boolean flagActionItems() {
-        final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
-        final int itemsSize = visibleItems.size();
+        final ArrayList<MenuItemImpl> visibleItems;
+        final int itemsSize;
+        if (mMenu != null) {
+            visibleItems = mMenu.getVisibleItems();
+            itemsSize = visibleItems.size();
+        } else {
+            visibleItems = null;
+            itemsSize = 0;
+        }
+
         int maxActions = mMaxItems;
         int widthLimit = mActionItemWidthLimit;
         final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
@@ -565,8 +593,8 @@
         if (isVisible) {
             // Not a submenu, but treat it like one.
             super.onSubMenuSelected(null);
-        } else {
-            mMenu.close(false);
+        } else if (mMenu != null) {
+            mMenu.close(false /* closeAllMenus */);
         }
     }
 
@@ -619,9 +647,9 @@
             setVisibility(VISIBLE);
             setEnabled(true);
 
-            setOnTouchListener(new ListPopupWindow.ForwardingListener(this) {
+            setOnTouchListener(new ForwardingListener(this) {
                 @Override
-                public ListPopupWindow getPopup() {
+                public ShowableListMenu getPopup() {
                     if (mOverflowPopup == null) {
                         return null;
                     }
@@ -695,31 +723,27 @@
     }
 
     private class OverflowPopup extends MenuPopupHelper {
-
         public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
                 boolean overflowOnly) {
             super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
             setGravity(GravityCompat.END);
-            setCallback(mPopupPresenterCallback);
+            setPresenterCallback(mPopupPresenterCallback);
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             if (mMenu != null) {
                 mMenu.close();
             }
             mOverflowPopup = null;
+
+            super.onDismiss();
         }
     }
 
     private class ActionButtonSubmenu extends MenuPopupHelper {
-        private SubMenuBuilder mSubMenu;
-
-        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
-            super(context, subMenu, null, false,
-                    R.attr.actionOverflowMenuStyle);
-            mSubMenu = subMenu;
+        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
+            super(context, subMenu, anchorView, false, R.attr.actionOverflowMenuStyle);
 
             MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
             if (!item.isActionButton()) {
@@ -727,30 +751,19 @@
                 setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
             }
 
-            setCallback(mPopupPresenterCallback);
-
-            boolean preserveIconSpacing = false;
-            final int count = subMenu.size();
-            for (int i = 0; i < count; i++) {
-                MenuItem childItem = subMenu.getItem(i);
-                if (childItem.isVisible() && childItem.getIcon() != null) {
-                    preserveIconSpacing = true;
-                    break;
-                }
-            }
-            setForceShowIcon(preserveIconSpacing);
+            setPresenterCallback(mPopupPresenterCallback);
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             mActionButtonPopup = null;
             mOpenSubMenuId = 0;
+
+            super.onDismiss();
         }
     }
 
     private class PopupPresenterCallback implements Callback {
-
         @Override
         public boolean onOpenSubMenu(MenuBuilder subMenu) {
             if (subMenu == null) return false;
@@ -763,7 +776,7 @@
         @Override
         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
             if (menu instanceof SubMenuBuilder) {
-                ((SubMenuBuilder) menu).getRootMenu().close(false);
+                menu.getRootMenu().close(false /* closeAllMenus */);
             }
             final Callback cb = getCallback();
             if (cb != null) {
@@ -780,7 +793,9 @@
         }
 
         public void run() {
-            mMenu.changeMenuMode();
+            if (mMenu != null) {
+                mMenu.changeMenuMode();
+            }
             final View menuView = (View) mMenuView;
             if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
                 mOverflowPopup = mPopup;
@@ -791,7 +806,7 @@
 
     private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
         @Override
-        public ListPopupWindow getPopup() {
+        public ShowableListMenu getPopup() {
             return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
         }
     }
diff --git a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
index 3ea3a2e..8d5c45d 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
@@ -27,6 +27,7 @@
 import android.support.v4.view.ActionProvider;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -237,9 +238,9 @@
 
         final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
         expandButton.setOnClickListener(mCallbacks);
-        expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
+        expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
             @Override
-            public ListPopupWindow getPopup() {
+            public ShowableListMenu getPopup() {
                 return getListPopupWindow();
             }
 
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
index 62c2ab7..9f35443 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
@@ -17,7 +17,6 @@
 package android.support.v7.widget;
 
 import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
@@ -43,7 +42,7 @@
     }
 
     void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
-        TypedArray a = mView.getContext().obtainStyledAttributes(attrs,
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
                 R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
         try {
             if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java b/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java
new file mode 100644
index 0000000..3024280
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatColorStateListInflater.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.ColorUtils;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.util.Xml;
+
+import java.io.IOException;
+
+class AppCompatColorStateListInflater {
+
+    private static final int DEFAULT_COLOR = Color.RED;
+
+    /**
+     * Creates a ColorStateList from an XML document using given a set of
+     * {@link Resources} and a {@link Theme}.
+     *
+     * @param r Resources against which the ColorStateList should be inflated.
+     * @param parser Parser for the XML document defining the ColorStateList.
+     * @param theme Optional theme to apply to the color state list, may be
+     *              {@code null}.
+     * @return A new color state list.
+     */
+    @NonNull
+    public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
+        final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.START_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
+            // Seek parser to start tag.
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        return createFromXmlInner(r, parser, attrs, theme);
+    }
+
+    /**
+     * Create from inside an XML document. Called on a parser positioned at a
+     * tag in an XML document, tries to create a ColorStateList from that tag.
+     *
+     * @throws XmlPullParserException if the current tag is not &lt;selector>
+     * @return A new color state list for the current tag.
+     */
+    @NonNull
+    private static ColorStateList createFromXmlInner(@NonNull Resources r,
+            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
+            @Nullable Resources.Theme theme)
+            throws XmlPullParserException, IOException {
+        final String name = parser.getName();
+        if (!name.equals("selector")) {
+            throw new XmlPullParserException(
+                    parser.getPositionDescription() + ": invalid color state list tag " + name);
+        }
+
+        return inflate(r, parser, attrs, theme);
+    }
+
+    /**
+     * Fill in this object based on the contents of an XML "selector" element.
+     */
+    private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
+            throws XmlPullParserException, IOException {
+        final int innerDepth = parser.getDepth() + 1;
+        int depth;
+        int type;
+        int defaultColor = DEFAULT_COLOR;
+
+        int[][] stateSpecList = new int[20][];
+        int[] colorList = new int[stateSpecList.length];
+        int listSize = 0;
+
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG || depth > innerDepth
+                    || !parser.getName().equals("item")) {
+                continue;
+            }
+
+            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
+            final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
+                    Color.MAGENTA);
+
+            float alphaMod = 1.0f;
+            if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
+                alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod);
+            } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) {
+                alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod);
+            }
+
+            a.recycle();
+
+            // Parse all unrecognized attributes as state specifiers.
+            int j = 0;
+            final int numAttrs = attrs.getAttributeCount();
+            int[] stateSpec = new int[numAttrs];
+            for (int i = 0; i < numAttrs; i++) {
+                final int stateResId = attrs.getAttributeNameResource(i);
+                if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha
+                        && stateResId != R.attr.alpha) {
+                    // Unrecognized attribute, add to state set
+                    stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+                            ? stateResId : -stateResId;
+                }
+            }
+            stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+            // Apply alpha modulation. If we couldn't resolve the color or
+            // alpha yet, the default values leave us enough information to
+            // modulate again during applyTheme().
+            final int color = modulateColorAlpha(baseColor, alphaMod);
+            if (listSize == 0 || stateSpec.length == 0) {
+                defaultColor = color;
+            }
+
+            colorList = GrowingArrayUtils.append(colorList, listSize, color);
+            stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
+            listSize++;
+        }
+
+        int[] colors = new int[listSize];
+        int[][] stateSpecs = new int[listSize][];
+        System.arraycopy(colorList, 0, colors, 0, listSize);
+        System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize);
+
+        return new ColorStateList(stateSpecs, colors);
+    }
+
+    private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
+            AttributeSet set, int[] attrs) {
+        return theme == null ? res.obtainAttributes(set, attrs)
+                : theme.obtainStyledAttributes(set, attrs, 0, 0);
+    }
+
+    private static int modulateColorAlpha(int color, float alphaMod) {
+        return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod));
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 33f5188..4a2345e 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -51,6 +51,7 @@
 import java.lang.ref.WeakReference;
 import java.util.WeakHashMap;
 
+import static android.support.v7.widget.ColorStateListUtils.getColorStateList;
 import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
 import static android.support.v7.widget.ThemeUtils.getThemeAttrColor;
 import static android.support.v7.widget.ThemeUtils.getThemeAttrColorStateList;
@@ -84,7 +85,7 @@
 
     private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) {
         final int sdk = Build.VERSION.SDK_INT;
-        if (sdk < 21) {
+        if (sdk < 23) {
             // We only want to use the automatic VectorDrawableCompat handling where it's
             // needed: on devices running before Lollipop
             manager.addDelegate("vector", new VdcInflateDelegate());
@@ -113,18 +114,8 @@
      * {@link DrawableCompat}'s tinting functionality.
      */
     private static final int[] TINT_COLOR_CONTROL_NORMAL = {
-            R.drawable.abc_ic_ab_back_mtrl_am_alpha,
-            R.drawable.abc_ic_go_search_api_mtrl_alpha,
-            R.drawable.abc_ic_search_api_mtrl_alpha,
             R.drawable.abc_ic_commit_search_api_mtrl_alpha,
-            R.drawable.abc_ic_clear_mtrl_alpha,
-            R.drawable.abc_ic_menu_share_mtrl_alpha,
-            R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
-            R.drawable.abc_ic_menu_cut_mtrl_alpha,
-            R.drawable.abc_ic_menu_selectall_mtrl_alpha,
-            R.drawable.abc_ic_menu_paste_mtrl_am_alpha,
-            R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
-            R.drawable.abc_ic_voice_search_api_mtrl_alpha
+            R.drawable.abc_seekbar_tick_mark_material
     };
 
     /**
@@ -153,16 +144,8 @@
      * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}
      */
     private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {
-            R.drawable.abc_edit_text_material,
             R.drawable.abc_tab_indicator_material,
-            R.drawable.abc_textfield_search_material,
-            R.drawable.abc_spinner_mtrl_am_alpha,
-            R.drawable.abc_spinner_textfield_background_material,
-            R.drawable.abc_ratingbar_full_material,
-            R.drawable.abc_switch_track_mtrl_alpha,
-            R.drawable.abc_switch_thumb_material,
-            R.drawable.abc_btn_default_mtrl_shape,
-            R.drawable.abc_btn_borderless_material
+            R.drawable.abc_textfield_search_material
     };
 
     /**
@@ -193,6 +176,8 @@
 
     public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,
             boolean failIfNotKnown) {
+        checkVectorDrawableSetup(context);
+
         Drawable drawable = loadDrawableFromDelegates(context, resId);
         if (drawable == null) {
             drawable = ContextCompat.getDrawable(context, resId);
@@ -237,7 +222,8 @@
                     getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
             setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
                     getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
-        } else if (resId == R.drawable.abc_ratingbar_indicator_material
+        } else if (resId == R.drawable.abc_ratingbar_material
+                || resId == R.drawable.abc_ratingbar_indicator_material
                 || resId == R.drawable.abc_ratingbar_small_material) {
             LayerDrawable ld = (LayerDrawable) drawable;
             setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
@@ -418,6 +404,9 @@
             colorAttr = android.R.attr.colorForeground;
             colorAttrSet = true;
             alpha = Math.round(0.16f * 255);
+        } else if (resId == R.drawable.abc_dialog_material_background) {
+            colorAttr = android.R.attr.colorBackground;
+            colorAttrSet = true;
         }
 
         if (colorAttrSet) {
@@ -481,11 +470,11 @@
         if (tint == null) {
             // ...if the cache did not contain a color state list, try and create one
             if (resId == R.drawable.abc_edit_text_material) {
-                tint = createEditTextColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_edittext);
             } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
-                tint = createSwitchTrackColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_switch_track);
             } else if (resId == R.drawable.abc_switch_thumb_material) {
-                tint = createSwitchThumbColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_switch_thumb);
             } else if (resId == R.drawable.abc_btn_default_mtrl_shape) {
                 tint = createDefaultButtonColorStateList(context);
             } else if (resId == R.drawable.abc_btn_borderless_material) {
@@ -494,15 +483,15 @@
                 tint = createColoredButtonColorStateList(context);
             } else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
                     || resId == R.drawable.abc_spinner_textfield_background_material) {
-                tint = createSpinnerColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_spinner);
             } else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {
                 tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);
             } else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
-                tint = createDefaultColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_default);
             } else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {
-                tint = createCheckableButtonColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_btn_checkable);
             } else if (resId == R.drawable.abc_seekbar_thumb_material) {
-                tint = createSeekbarThumbColorStateList(context);
+                tint = getColorStateList(context, R.color.abc_tint_seek_thumb);
             }
 
             if (tint != null) {
@@ -533,164 +522,6 @@
         themeTints.append(resId, tintList);
     }
 
-    private ColorStateList createDefaultColorStateList(Context context) {
-        /**
-         * Generate the default color state list which uses the colorControl attributes.
-         * Order is important here. The default enabled state needs to go at the bottom.
-         */
-
-        final int colorControlNormal = getThemeAttrColor(context, R.attr.colorControlNormal);
-        final int colorControlActivated = getThemeAttrColor(context, R.attr.colorControlActivated);
-
-        final int[][] states = new int[7][];
-        final int[] colors = new int[7];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        states[i] = ThemeUtils.FOCUSED_STATE_SET;
-        colors[i] = colorControlActivated;
-        i++;
-
-        states[i] = ThemeUtils.ACTIVATED_STATE_SET;
-        colors[i] = colorControlActivated;
-        i++;
-
-        states[i] = ThemeUtils.PRESSED_STATE_SET;
-        colors[i] = colorControlActivated;
-        i++;
-
-        states[i] = ThemeUtils.CHECKED_STATE_SET;
-        colors[i] = colorControlActivated;
-        i++;
-
-        states[i] = ThemeUtils.SELECTED_STATE_SET;
-        colors[i] = colorControlActivated;
-        i++;
-
-        // Default enabled state
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = colorControlNormal;
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
-    private ColorStateList createCheckableButtonColorStateList(Context context) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        states[i] = ThemeUtils.CHECKED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-        i++;
-
-        // Default enabled state
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
-    private ColorStateList createSwitchTrackColorStateList(Context context) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.1f);
-        i++;
-
-        states[i] = ThemeUtils.CHECKED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated, 0.3f);
-        i++;
-
-        // Default enabled state
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.3f);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
-    private ColorStateList createSwitchThumbColorStateList(Context context) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        final ColorStateList thumbColor = getThemeAttrColorStateList(context,
-                R.attr.colorSwitchThumbNormal);
-
-        if (thumbColor != null && thumbColor.isStateful()) {
-            // If colorSwitchThumbNormal is a valid ColorStateList, extract the default and
-            // disabled colors from it
-
-            // Disabled state
-            states[i] = ThemeUtils.DISABLED_STATE_SET;
-            colors[i] = thumbColor.getColorForState(states[i], 0);
-            i++;
-
-            states[i] = ThemeUtils.CHECKED_STATE_SET;
-            colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-            i++;
-
-            // Default enabled state
-            states[i] = ThemeUtils.EMPTY_STATE_SET;
-            colors[i] = thumbColor.getDefaultColor();
-            i++;
-        } else {
-            // Else we'll use an approximation using the default disabled alpha
-
-            // Disabled state
-            states[i] = ThemeUtils.DISABLED_STATE_SET;
-            colors[i] = getDisabledThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
-            i++;
-
-            states[i] = ThemeUtils.CHECKED_STATE_SET;
-            colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-            i++;
-
-            // Default enabled state
-            states[i] = ThemeUtils.EMPTY_STATE_SET;
-            colors[i] = getThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
-            i++;
-        }
-
-        return new ColorStateList(states, colors);
-    }
-
-    private ColorStateList createEditTextColorStateList(Context context) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        // Default enabled state
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
     private ColorStateList createDefaultButtonColorStateList(Context context) {
         return createButtonColorStateList(context,
                 getThemeAttrColor(context, R.attr.colorButtonNormal));
@@ -732,44 +563,6 @@
         return new ColorStateList(states, colors);
     }
 
-    private ColorStateList createSpinnerColorStateList(Context context) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
-        i++;
-
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
-    private ColorStateList createSeekbarThumbColorStateList(Context context) {
-        final int[][] states = new int[2][];
-        final int[] colors = new int[2];
-        int i = 0;
-
-        // Disabled state
-        states[i] = ThemeUtils.DISABLED_STATE_SET;
-        colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlActivated);
-        i++;
-
-        states[i] = ThemeUtils.EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
     private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
 
         public ColorFilterLruCache(int maxSize) {
@@ -844,6 +637,23 @@
         d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));
     }
 
+    private void checkVectorDrawableSetup(@NonNull Context context) {
+        if (!mHasCheckedVectorDrawableSetup) {
+            // We've already checked so return now...
+            return;
+        }
+        // Here we will check that a known Vector drawable resource inside AppCompat can be
+        // correctly decoded. We use one that will almost definitely be used in the future to
+        // negate any wasted work
+        final Drawable d = getDrawable(context, R.drawable.abc_ic_ab_back_material);
+        if (d != null && isVectorDrawable(d)) {
+            mHasCheckedVectorDrawableSetup = true;
+        } else {
+            throw new IllegalStateException("This app has been built with an incorrect "
+                    + "configuration. Please configure your build for VectorDrawableCompat.");
+        }
+    }
+
     private static boolean isVectorDrawable(@NonNull Drawable d) {
         return d instanceof VectorDrawableCompat
                 || PLATFORM_VD_CLAZZ.equals(d.getClass().getName());
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
index 4f3ee05..5355660 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
@@ -17,6 +17,7 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.graphics.Canvas;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
 import android.widget.SeekBar;
@@ -49,4 +50,21 @@
         mAppCompatSeekBarHelper.loadFromAttributes(attrs, defStyleAttr);
     }
 
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mAppCompatSeekBarHelper.drawTickMarks(canvas);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        mAppCompatSeekBarHelper.drawableStateChanged();
+    }
+
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        super.jumpDrawablesToCurrentState();
+        mAppCompatSeekBarHelper.jumpDrawablesToCurrentState();
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
index 2af2acc..e26faee 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
@@ -16,18 +16,27 @@
 
 package android.support.v7.widget;
 
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
 import android.widget.SeekBar;
 
 class AppCompatSeekBarHelper extends AppCompatProgressBarHelper {
 
-    private static final int[] TINT_ATTRS = {
-            android.R.attr.thumb
-    };
-
     private final SeekBar mView;
 
+    private Drawable mTickMark;
+    private ColorStateList mTickMarkTintList = null;
+    private PorterDuff.Mode mTickMarkTintMode = null;
+    private boolean mHasTickMarkTint = false;
+    private boolean mHasTickMarkTintMode = false;
+
     AppCompatSeekBarHelper(SeekBar view, AppCompatDrawableManager drawableManager) {
         super(view, drawableManager);
         mView = view;
@@ -37,11 +46,137 @@
         super.loadFromAttributes(attrs, defStyleAttr);
 
         TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
-                TINT_ATTRS, defStyleAttr, 0);
-        Drawable drawable = a.getDrawableIfKnown(0);
+                R.styleable.AppCompatSeekBar, defStyleAttr, 0);
+        final Drawable drawable = a.getDrawableIfKnown(R.styleable.AppCompatSeekBar_android_thumb);
         if (drawable != null) {
             mView.setThumb(drawable);
         }
+
+        final Drawable tickMark = a.getDrawable(R.styleable.AppCompatSeekBar_tickMark);
+        setTickMark(tickMark);
+
+        if (a.hasValue(R.styleable.AppCompatSeekBar_tickMarkTintMode)) {
+            mTickMarkTintMode = DrawableUtils.parseTintMode(a.getInt(
+                    R.styleable.AppCompatSeekBar_tickMarkTintMode, -1), mTickMarkTintMode);
+            mHasTickMarkTintMode = true;
+        }
+
+        if (a.hasValue(R.styleable.AppCompatSeekBar_tickMarkTint)) {
+            mTickMarkTintList = a.getColorStateList(R.styleable.AppCompatSeekBar_tickMarkTint);
+            mHasTickMarkTint = true;
+        }
+
         a.recycle();
+
+        applyTickMarkTint();
     }
+
+    void setTickMark(@Nullable Drawable tickMark) {
+        if (mTickMark != null) {
+            mTickMark.setCallback(null);
+        }
+
+        mTickMark = tickMark;
+
+        if (tickMark != null) {
+            tickMark.setCallback(mView);
+            DrawableCompat.setLayoutDirection(tickMark, ViewCompat.getLayoutDirection(mView));
+            if (tickMark.isStateful()) {
+                tickMark.setState(mView.getDrawableState());
+            }
+            applyTickMarkTint();
+        }
+
+        mView.invalidate();
+    }
+
+    @Nullable
+    Drawable getTickMark() {
+        return mTickMark;
+    }
+
+    void setTickMarkTintList(@Nullable ColorStateList tint) {
+        mTickMarkTintList = tint;
+        mHasTickMarkTint = true;
+
+        applyTickMarkTint();
+    }
+
+    @Nullable
+    ColorStateList getTickMarkTintList() {
+        return mTickMarkTintList;
+    }
+
+    void setTickMarkTintMode(@Nullable PorterDuff.Mode tintMode) {
+        mTickMarkTintMode = tintMode;
+        mHasTickMarkTintMode = true;
+
+        applyTickMarkTint();
+    }
+
+    @Nullable
+    PorterDuff.Mode getTickMarkTintMode() {
+        return mTickMarkTintMode;
+    }
+
+    private void applyTickMarkTint() {
+        if (mTickMark != null && (mHasTickMarkTint || mHasTickMarkTintMode)) {
+            mTickMark = mTickMark.mutate();
+
+            if (mHasTickMarkTint) {
+                mTickMark.setTintList(mTickMarkTintList);
+            }
+
+            if (mHasTickMarkTintMode) {
+                mTickMark.setTintMode(mTickMarkTintMode);
+            }
+
+            // The drawable (or one of its children) may not have been
+            // stateful before applying the tint, so let's try again.
+            if (mTickMark.isStateful()) {
+                mTickMark.setState(mView.getDrawableState());
+            }
+        }
+    }
+
+    void jumpDrawablesToCurrentState() {
+        if (mTickMark != null) {
+            mTickMark.jumpToCurrentState();
+        }
+    }
+
+    void drawableStateChanged() {
+        final Drawable tickMark = mTickMark;
+        if (tickMark != null && tickMark.isStateful()
+                && tickMark.setState(mView.getDrawableState())) {
+            mView.invalidateDrawable(tickMark);
+        }
+    }
+
+    /**
+     * Draw the tick marks.
+     */
+    void drawTickMarks(Canvas canvas) {
+        if (mTickMark != null) {
+            final int count = mView.getMax();
+            if (count > 1) {
+                final int w = mTickMark.getIntrinsicWidth();
+                final int h = mTickMark.getIntrinsicHeight();
+                final int halfW = w >= 0 ? w / 2 : 1;
+                final int halfH = h >= 0 ? h / 2 : 1;
+                mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
+
+                final float spacing = (mView.getWidth() - mView.getPaddingLeft()
+                        - mView.getPaddingRight()) / (float) count;
+                final int saveCount = canvas.save();
+                canvas.translate(mView.getPaddingLeft(), mView.getHeight() / 2);
+                for (int i = 0; i <= count; i++) {
+                    mTickMark.draw(canvas);
+                    canvas.translate(spacing, 0);
+                }
+                canvas.restoreToCount(saveCount);
+            }
+        }
+    }
+
 }
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
index 365e376..0e14edc 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
@@ -32,6 +32,7 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.view.ContextThemeWrapper;
+import android.support.v7.view.menu.ShowableListMenu;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -48,14 +49,14 @@
 
 
 /**
- * A {@link Spinner} which supports compatible features on older version of the platform,
+ * A {@link Spinner} which supports compatible features on older versions of the platform,
  * including:
  * <ul>
- * <li>Allows dynamic tint of it background via the background tint methods in
+ * <li>Dynamic tinting of the background via the background tint methods in
  * {@link android.support.v4.view.ViewCompat}.</li>
- * <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
+ * <li>Configuring the background tint using {@link R.attr#backgroundTint} and
  * {@link R.attr#backgroundTintMode}.</li>
- * <li>Allows setting of the popups theme using {@link R.attr#popupTheme}.</li>
+ * <li>Setting the popup theme using {@link R.attr#popupTheme}.</li>
  * </ul>
  *
  * <p>This will automatically be used when you use {@link Spinner} in your layouts.
@@ -84,7 +85,7 @@
     private Context mPopupContext;
 
     /** Forwarding listener used to implement drag-to-open. */
-    private ListPopupWindow.ForwardingListener mForwardingListener;
+    private ForwardingListener mForwardingListener;
 
     /** Temporary holder for setAdapter() calls from the super constructor. */
     private SpinnerAdapter mTempAdapter;
@@ -250,9 +251,9 @@
                 pa.recycle();
 
                 mPopup = popup;
-                mForwardingListener = new ListPopupWindow.ForwardingListener(this) {
+                mForwardingListener = new ForwardingListener(this) {
                     @Override
-                    public ListPopupWindow getPopup() {
+                    public ShowableListMenu getPopup() {
                         return popup;
                     }
 
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index 2cd4c04..b74a89f 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -25,6 +25,7 @@
 import android.support.v7.text.AllCapsTransformationMethod;
 import android.text.method.PasswordTransformationMethod;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.widget.TextView;
 
 class AppCompatTextHelper {
@@ -36,10 +37,13 @@
         return new AppCompatTextHelper(textView);
     }
 
-    private static final int[] VIEW_ATTRS = {android.R.attr.textAppearance,
-            android.R.attr.drawableLeft, android.R.attr.drawableTop,
-            android.R.attr.drawableRight, android.R.attr.drawableBottom };
-    private static final int[] TEXT_APPEARANCE_ATTRS = {R.attr.textAllCaps};
+    private static final int[] VIEW_ATTRS = {
+            android.R.attr.textAppearance,
+            android.R.attr.drawableLeft,
+            android.R.attr.drawableTop,
+            android.R.attr.drawableRight,
+            android.R.attr.drawableBottom
+    };
 
     final TextView mView;
 
@@ -57,9 +61,9 @@
         final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
 
         // First read the TextAppearance style id
-        TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+                VIEW_ATTRS, defStyleAttr, 0);
         final int ap = a.getResourceId(0, -1);
-
         // Now read the compound drawable and grab any tints
         if (a.hasValue(1)) {
             mDrawableLeftTint = createTintInfo(context, drawableManager, a.getResourceId(1, 0));
@@ -75,48 +79,75 @@
         }
         a.recycle();
 
-        if (!(mView.getTransformationMethod() instanceof PasswordTransformationMethod)) {
-            // PasswordTransformationMethod wipes out all other TransformationMethod instances
-            // in TextView's constructor, so we should only set a new transformation method
-            // if we don't have a PasswordTransformationMethod currently...
+        // PasswordTransformationMethod wipes out all other TransformationMethod instances
+        // in TextView's constructor, so we should only set a new transformation method
+        // if we don't have a PasswordTransformationMethod currently...
+        final boolean hasPwdTm =
+                mView.getTransformationMethod() instanceof PasswordTransformationMethod;
+        boolean allCaps = false;
+        boolean allCapsSet = false;
+        ColorStateList textColor = null;
 
-            boolean allCaps = false;
-            boolean allCapsSet = false;
-
-            // First check TextAppearance's textAllCaps value
-            if (ap != -1) {
-                TypedArray appearance = context
-                        .obtainStyledAttributes(ap, R.styleable.TextAppearance);
-                if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
-                    allCapsSet = true;
-                    allCaps = appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
-                }
-                appearance.recycle();
-            }
-
-            // Now read the style's value
-            a = context.obtainStyledAttributes(attrs, TEXT_APPEARANCE_ATTRS, defStyleAttr, 0);
-            if (a.hasValue(0)) {
+        // First check TextAppearance's textAllCaps value
+        if (ap != -1) {
+            a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
+            if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
                 allCapsSet = true;
-                allCaps = a.getBoolean(0, false);
+                allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
+            }
+            if (Build.VERSION.SDK_INT < 23
+                    && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+                // If we're running on < API 23, the text color may contain theme references
+                // so let's re-set using our own inflater
+                textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
             }
             a.recycle();
+        }
 
-            if (allCapsSet) {
-                setAllCaps(allCaps);
-            }
+        // Now read the style's values
+        a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
+                defStyleAttr, 0);
+        if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+            allCapsSet = true;
+            allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
+        }
+        if (Build.VERSION.SDK_INT < 23
+                && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+            // If we're running on < API 23, the text color may contain theme references
+            // so let's re-set using our own inflater
+            textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+        }
+        a.recycle();
+
+        if (textColor != null) {
+            mView.setTextColor(textColor);
+        }
+
+        if (!hasPwdTm && allCapsSet) {
+            setAllCaps(allCaps);
         }
     }
 
     void onSetTextAppearance(Context context, int resId) {
-        TypedArray appearance = context.obtainStyledAttributes(resId, TEXT_APPEARANCE_ATTRS);
-        if (appearance.getBoolean(0, false)) {
+        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
+                resId, R.styleable.TextAppearance);
+        if (a.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
             // This follows the logic in TextView.setTextAppearance that serves as an "overlay"
             // on the current state of the TextView. Here we only allow turning all-caps on when
             // the passed style has textAllCaps attribute set to true.
             setAllCaps(true);
         }
-        appearance.recycle();
+        if (Build.VERSION.SDK_INT < 23
+                && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+            // If we're running on < API 23, the text color may contain theme references
+            // so let's re-set using our own inflater
+            final ColorStateList textColor
+                    = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+            if (textColor != null) {
+                mView.setTextColor(textColor);
+            }
+        }
+        a.recycle();
     }
 
     void setAllCaps(boolean allCaps) {
diff --git a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index 1b8c9a5..a2827b6 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.support.v4.content.res.ConfigurationHelper;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
@@ -32,6 +33,9 @@
  * @hide
  */
 public class ButtonBarLayout extends LinearLayout {
+    // Whether to allow vertically stacked button bars. This is disabled for
+    // configurations with a small (e.g. less than 320dp) screen height. -->
+    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
 
     /** Whether the current configuration allows stacking. */
     private boolean mAllowStacking;
@@ -39,8 +43,12 @@
 
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
+        final boolean allowStackingDefault =
+                ConfigurationHelper.getScreenHeightDp(getResources())
+                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+                allowStackingDefault);
         ta.recycle();
     }
 
@@ -124,4 +132,4 @@
     private boolean isStacked() {
         return getOrientation() == LinearLayout.VERTICAL;
     }
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java b/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java
new file mode 100644
index 0000000..d5ff6c9
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ColorStateListUtils.java
@@ -0,0 +1,93 @@
+/*
+ * 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.widget;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.util.TypedValue;
+
+class ColorStateListUtils {
+
+    private static final String LOG_TAG = "ColorStateListUtils";
+    private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
+
+    private ColorStateListUtils() {}
+
+    /**
+     * Returns the {@link ColorStateList} from the given resource. The resource can include
+     * themeable attributes.
+     */
+    static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int resId) {
+        if (Build.VERSION.SDK_INT >= 23) {
+            // On M+ we can use the framework
+            return context.getColorStateList(resId);
+        }
+        // Before that, we'll try and inflate it manually
+        final ColorStateList csl = inflateColorStateList(context, resId);
+        if (csl != null) {
+            return csl;
+        }
+        // If we reach here then we couldn't inflate it, so let the framework handle it
+        return ContextCompat.getColorStateList(context, resId);
+    }
+
+    /**
+     * Inflates a {@link ColorStateList} from resources, honouring theme attributes.
+     */
+    private static ColorStateList inflateColorStateList(Context context, int resId) {
+        if (isColorInt(context, resId)) {
+            // The resource is a color int, we can't handle it so return null
+            return null;
+        }
+
+        final Resources r = context.getResources();
+        final XmlPullParser xml = r.getXml(resId);
+        try {
+            return AppCompatColorStateListInflater.createFromXml(r, xml, context.getTheme());
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Failed to inflate ColorStateList, leaving it to the framework", e);
+        }
+        return null;
+    }
+
+    static boolean isColorInt(Context context, int resId) {
+        final Resources r = context.getResources();
+
+        final TypedValue value = getTypedValue();
+        r.getValue(resId, value, true);
+
+        return value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+                && value.type <= TypedValue.TYPE_LAST_COLOR_INT;
+    }
+
+    private static TypedValue getTypedValue() {
+        TypedValue tv = TL_TYPED_VALUE.get();
+        if (tv == null) {
+            tv = new TypedValue();
+            TL_TYPED_VALUE.set(tv);
+        }
+        return tv;
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java b/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
index 9528afe..015cb4b 100644
--- a/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
+++ b/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
@@ -120,24 +120,26 @@
      * there is a known issue in the given drawable's implementation.
      */
     public static boolean canSafelyMutateDrawable(@NonNull Drawable drawable) {
-        if (drawable instanceof LayerDrawable) {
-            return Build.VERSION.SDK_INT >= 16;
-        } else if (drawable instanceof InsetDrawable) {
-            return Build.VERSION.SDK_INT >= 14;
-        } else if (drawable instanceof StateListDrawable) {
+        if (Build.VERSION.SDK_INT < 8 && drawable instanceof StateListDrawable) {
             // StateListDrawable has a bug in mutate() on API 7
-            return Build.VERSION.SDK_INT >= 8;
-        } else if (drawable instanceof GradientDrawable) {
+            return false;
+        } else if (Build.VERSION.SDK_INT < 15 && drawable instanceof InsetDrawable) {
+            return false;
+        }  else if (Build.VERSION.SDK_INT < 15 && drawable instanceof GradientDrawable) {
             // GradientDrawable has a bug pre-ICS which results in mutate() resulting
             // in loss of color
-            return Build.VERSION.SDK_INT >= 14;
-        } else if (drawable instanceof DrawableContainer) {
+            return false;
+        } else if (Build.VERSION.SDK_INT < 17 && drawable instanceof LayerDrawable) {
+            return false;
+        }
+
+        if (drawable instanceof DrawableContainer) {
             // If we have a DrawableContainer, let's traverse it's child array
             final Drawable.ConstantState state = drawable.getConstantState();
             if (state instanceof DrawableContainer.DrawableContainerState) {
                 final DrawableContainer.DrawableContainerState containerState =
                         (DrawableContainer.DrawableContainerState) state;
-                for (Drawable child : containerState.getChildren()) {
+                for (final Drawable child : containerState.getChildren()) {
                     if (!canSafelyMutateDrawable(child)) {
                         return false;
                     }
@@ -152,6 +154,7 @@
                     ((android.support.v7.graphics.drawable.DrawableWrapper) drawable)
                             .getWrappedDrawable());
         }
+
         return true;
     }
 
diff --git a/v7/appcompat/src/android/support/v7/widget/DropDownListView.java b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
new file mode 100644
index 0000000..f6a75f7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
@@ -0,0 +1,277 @@
+/*
+ * 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 android.content.Context;
+import android.os.Build;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.widget.ListViewAutoScrollHelper;
+import android.support.v7.appcompat.R;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.</p>
+ */
+class DropDownListView extends ListViewCompat {
+
+    /*
+    * WARNING: This is a workaround for a touch mode issue.
+    *
+    * Touch mode is propagated lazily to windows. This causes problems in
+    * the following scenario:
+    * - Type something in the AutoCompleteTextView and get some results
+    * - Move down with the d-pad to select an item in the list
+    * - Move up with the d-pad until the selection disappears
+    * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+    *   and get new results; you are now in touch mode
+    * - The selection comes back on the first item in the list, even though
+    *   the list is supposed to be in touch mode
+    *
+    * Using the soft keyboard triggers the touch mode change but that change
+    * is propagated to our window only after the first list layout, therefore
+    * after the list attempts to resurrect the selection.
+    *
+    * The trick to work around this issue is to pretend the list is in touch
+    * mode when we know that the selection should not appear, that is when
+    * we know the user moved the selection away from the list.
+    *
+    * This boolean is set to true whenever we explicitly hide the list's
+    * selection and reset to false whenever we know the user moved the
+    * selection back to the list.
+    *
+    * When this boolean is true, isInTouchMode() returns true, otherwise it
+    * returns super.isInTouchMode().
+    */
+    private boolean mListSelectionHidden;
+
+    /**
+     * True if this wrapper should fake focus.
+     */
+    private boolean mHijackFocus;
+
+    /** Whether to force drawing of the pressed state selector. */
+    private boolean mDrawsInPressedState;
+
+    /** Current drag-to-open click animation, if any. */
+    private ViewPropertyAnimatorCompat mClickAnimation;
+
+    /** Helper for drag-to-open auto scrolling. */
+    private ListViewAutoScrollHelper mScrollHelper;
+
+    /**
+     * <p>Creates a new list view wrapper.</p>
+     *
+     * @param context this view's context
+     */
+    public DropDownListView(Context context, boolean hijackFocus) {
+        super(context, null, R.attr.dropDownListViewStyle);
+        mHijackFocus = hijackFocus;
+        setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+    }
+
+    /**
+     * Handles forwarded events.
+     *
+     * @param activePointerId id of the pointer that activated forwarding
+     * @return whether the event was handled
+     */
+    public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+        boolean handledEvent = true;
+        boolean clearPressedItem = false;
+
+        final int actionMasked = MotionEventCompat.getActionMasked(event);
+        switch (actionMasked) {
+            case MotionEvent.ACTION_CANCEL:
+                handledEvent = false;
+                break;
+            case MotionEvent.ACTION_UP:
+                handledEvent = false;
+                // $FALL-THROUGH$
+            case MotionEvent.ACTION_MOVE:
+                final int activeIndex = event.findPointerIndex(activePointerId);
+                if (activeIndex < 0) {
+                    handledEvent = false;
+                    break;
+                }
+
+                final int x = (int) event.getX(activeIndex);
+                final int y = (int) event.getY(activeIndex);
+                final int position = pointToPosition(x, y);
+                if (position == INVALID_POSITION) {
+                    clearPressedItem = true;
+                    break;
+                }
+
+                final View child = getChildAt(position - getFirstVisiblePosition());
+                setPressedItem(child, position, x, y);
+                handledEvent = true;
+
+                if (actionMasked == MotionEvent.ACTION_UP) {
+                    clickPressedItem(child, position);
+                }
+                break;
+        }
+
+        // Failure to handle the event cancels forwarding.
+        if (!handledEvent || clearPressedItem) {
+            clearPressedItem();
+        }
+
+        // Manage automatic scrolling.
+        if (handledEvent) {
+            if (mScrollHelper == null) {
+                mScrollHelper = new ListViewAutoScrollHelper(this);
+            }
+            mScrollHelper.setEnabled(true);
+            mScrollHelper.onTouch(this, event);
+        } else if (mScrollHelper != null) {
+            mScrollHelper.setEnabled(false);
+        }
+
+        return handledEvent;
+    }
+
+    /**
+     * Starts an alpha animation on the selector. When the animation ends,
+     * the list performs a click on the item.
+     */
+    private void clickPressedItem(final View child, final int position) {
+        final long id = getItemIdAtPosition(position);
+        performItemClick(child, position, id);
+    }
+
+    /**
+     * Sets whether the list selection is hidden, as part of a workaround for a
+     * touch mode issue (see the declaration for mListSelectionHidden).
+     *
+     * @param hideListSelection {@code true} to hide list selection,
+     *                          {@code false} to show
+     */
+    void setListSelectionHidden(boolean hideListSelection) {
+        mListSelectionHidden = hideListSelection;
+    }
+
+    private void clearPressedItem() {
+        mDrawsInPressedState = false;
+        setPressed(false);
+        // This will call through to updateSelectorState()
+        drawableStateChanged();
+
+        final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+        if (motionView != null) {
+            motionView.setPressed(false);
+        }
+
+        if (mClickAnimation != null) {
+            mClickAnimation.cancel();
+            mClickAnimation = null;
+        }
+    }
+
+    private void setPressedItem(View child, int position, float x, float y) {
+        mDrawsInPressedState = true;
+
+        // Ordering is essential. First, update the container's pressed state.
+        if (Build.VERSION.SDK_INT >= 21) {
+            drawableHotspotChanged(x, y);
+        }
+        if (!isPressed()) {
+            setPressed(true);
+        }
+
+        // Next, run layout to stabilize child positions.
+        layoutChildren();
+
+        // Manage the pressed view based on motion position. This allows us to
+        // play nicely with actual touch and scroll events.
+        if (mMotionPosition != INVALID_POSITION) {
+            final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+            if (motionView != null && motionView != child && motionView.isPressed()) {
+                motionView.setPressed(false);
+            }
+        }
+        mMotionPosition = position;
+
+        // Offset for child coordinates.
+        final float childX = x - child.getLeft();
+        final float childY = y - child.getTop();
+        if (Build.VERSION.SDK_INT >= 21) {
+            child.drawableHotspotChanged(childX, childY);
+        }
+        if (!child.isPressed()) {
+            child.setPressed(true);
+        }
+
+        // Ensure that keyboard focus starts from the last touched position.
+        positionSelectorLikeTouchCompat(position, child, x, y);
+
+        // This needs some explanation. We need to disable the selector for this next call
+        // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
+        // will draw the selector and bad things happen.
+        setSelectorEnabled(false);
+
+        // Refresh the drawable state to reflect the new pressed state,
+        // which will also update the selector state.
+        refreshDrawableState();
+    }
+
+    @Override
+    protected boolean touchModeDrawsInPressedStateCompat() {
+        return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
+    }
+
+    @Override
+    public boolean isInTouchMode() {
+        // WARNING: Please read the comment where mListSelectionHidden is declared
+        return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+    }
+
+    /**
+     * <p>Returns the focus state in the drop down.</p>
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean hasWindowFocus() {
+        return mHijackFocus || super.hasWindowFocus();
+    }
+
+    /**
+     * <p>Returns the focus state in the drop down.</p>
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean isFocused() {
+        return mHijackFocus || super.isFocused();
+    }
+
+    /**
+     * <p>Returns the focus state in the drop down.</p>
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean hasFocus() {
+        return mHijackFocus || super.hasFocus();
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
new file mode 100644
index 0000000..1ad9218
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
@@ -0,0 +1,306 @@
+/*
+ * 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 android.os.SystemClock;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.view.menu.ShowableListMenu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+
+/**
+ * Abstract class that forwards touch events to a {@link ShowableListMenu}.
+ *
+ * @hide
+ */
+public abstract class ForwardingListener implements View.OnTouchListener {
+    /** Scaled touch slop, used for detecting movement outside bounds. */
+    private final float mScaledTouchSlop;
+
+    /** Timeout before disallowing intercept on the source's parent. */
+    private final int mTapTimeout;
+
+    /** Timeout before accepting a long-press to start forwarding. */
+    private final int mLongPressTimeout;
+
+    /** Source view from which events are forwarded. */
+    private final View mSrc;
+
+    /** Runnable used to prevent conflicts with scrolling parents. */
+    private Runnable mDisallowIntercept;
+
+    /** Runnable used to trigger forwarding on long-press. */
+    private Runnable mTriggerLongPress;
+
+    /** Whether this listener is currently forwarding touch events. */
+    private boolean mForwarding;
+
+    /** The id of the first pointer down in the current event stream. */
+    private int mActivePointerId;
+
+    /**
+     * Temporary Matrix instance
+     */
+    private final int[] mTmpLocation = new int[2];
+
+    public ForwardingListener(View src) {
+        mSrc = src;
+        mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        // Use a medium-press timeout. Halfway between tap and long-press.
+        mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+    }
+
+    /**
+     * Returns the popup to which this listener is forwarding events.
+     * <p>
+     * Override this to return the correct popup. If the popup is displayed
+     * asynchronously, you may also need to override
+     * {@link #onForwardingStopped} to prevent premature cancelation of
+     * forwarding.
+     *
+     * @return the popup to which this listener is forwarding events
+     */
+    public abstract ShowableListMenu getPopup();
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        final boolean wasForwarding = mForwarding;
+        final boolean forwarding;
+        if (wasForwarding) {
+            forwarding = onTouchForwarded(event) || !onForwardingStopped();
+        } else {
+            forwarding = onTouchObserved(event) && onForwardingStarted();
+
+            if (forwarding) {
+                // Make sure we cancel any ongoing source event stream.
+                final long now = SystemClock.uptimeMillis();
+                final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+                        0.0f, 0.0f, 0);
+                mSrc.onTouchEvent(e);
+                e.recycle();
+            }
+        }
+
+        mForwarding = forwarding;
+        return forwarding || wasForwarding;
+    }
+
+    /**
+     * Called when forwarding would like to start.
+     * <p>
+     * By default, this will show the popup returned by {@link #getPopup()}.
+     * It may be overridden to perform another action, like clicking the
+     * source view or preparing the popup before showing it.
+     *
+     * @return true to start forwarding, false otherwise
+     */
+    protected boolean onForwardingStarted() {
+        final ShowableListMenu popup = getPopup();
+        if (popup != null && !popup.isShowing()) {
+            popup.show();
+        }
+        return true;
+    }
+
+    /**
+     * Called when forwarding would like to stop.
+     * <p>
+     * By default, this will dismiss the popup returned by
+     * {@link #getPopup()}. It may be overridden to perform some other
+     * action.
+     *
+     * @return true to stop forwarding, false otherwise
+     */
+    protected boolean onForwardingStopped() {
+        final ShowableListMenu popup = getPopup();
+        if (popup != null && popup.isShowing()) {
+            popup.dismiss();
+        }
+        return true;
+    }
+
+    /**
+     * Observes motion events and determines when to start forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to start forwarding motion events, false otherwise
+     */
+    private boolean onTouchObserved(MotionEvent srcEvent) {
+        final View src = mSrc;
+        if (!src.isEnabled()) {
+            return false;
+        }
+
+        final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+                mActivePointerId = srcEvent.getPointerId(0);
+
+                if (mDisallowIntercept == null) {
+                    mDisallowIntercept = new DisallowIntercept();
+                }
+                src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+                if (mTriggerLongPress == null) {
+                    mTriggerLongPress = new TriggerLongPress();
+                }
+                src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+                if (activePointerIndex >= 0) {
+                    final float x = srcEvent.getX(activePointerIndex);
+                    final float y = srcEvent.getY(activePointerIndex);
+
+                    // Has the pointer moved outside of the view?
+                    if (!pointInView(src, x, y, mScaledTouchSlop)) {
+                        clearCallbacks();
+
+                        // Don't let the parent intercept our events.
+                        src.getParent().requestDisallowInterceptTouchEvent(true);
+                        return true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                clearCallbacks();
+                break;
+        }
+
+        return false;
+    }
+
+    private void clearCallbacks() {
+        if (mTriggerLongPress != null) {
+            mSrc.removeCallbacks(mTriggerLongPress);
+        }
+
+        if (mDisallowIntercept != null) {
+            mSrc.removeCallbacks(mDisallowIntercept);
+        }
+    }
+
+    private void onLongPress() {
+        clearCallbacks();
+
+        final View src = mSrc;
+        if (!src.isEnabled() || src.isLongClickable()) {
+            // Ignore long-press if the view is disabled or has its own
+            // handler.
+            return;
+        }
+
+        if (!onForwardingStarted()) {
+            return;
+        }
+
+        // Don't let the parent intercept our events.
+        src.getParent().requestDisallowInterceptTouchEvent(true);
+
+        // Make sure we cancel any ongoing source event stream.
+        final long now = SystemClock.uptimeMillis();
+        final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        src.onTouchEvent(e);
+        e.recycle();
+
+        mForwarding = true;
+    }
+
+    /**
+     * Handles forwarded motion events and determines when to stop
+     * forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to continue forwarding motion events, false to cancel
+     */
+    private boolean onTouchForwarded(MotionEvent srcEvent) {
+        final View src = mSrc;
+        final ShowableListMenu popup = getPopup();
+        if (popup == null || !popup.isShowing()) {
+            return false;
+        }
+
+        final DropDownListView dst = (DropDownListView) popup.getListView();
+        if (dst == null || !dst.isShown()) {
+            return false;
+        }
+
+        // Convert event to destination-local coordinates.
+        final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+        toGlobalMotionEvent(src, dstEvent);
+        toLocalMotionEvent(dst, dstEvent);
+
+        // Forward converted event to destination view, then recycle it.
+        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+        dstEvent.recycle();
+
+        // Always cancel forwarding when the touch stream ends.
+        final int action = MotionEventCompat.getActionMasked(srcEvent);
+        final boolean keepForwarding = action != MotionEvent.ACTION_UP
+                && action != MotionEvent.ACTION_CANCEL;
+
+        return handled && keepForwarding;
+    }
+
+    private static boolean pointInView(View view, float localX, float localY, float slop) {
+        return localX >= -slop && localY >= -slop &&
+                localX < ((view.getRight() - view.getLeft()) + slop) &&
+                localY < ((view.getBottom() - view.getTop()) + slop);
+    }
+
+    /**
+     * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
+     * (scaleX, scaleY, etc).
+     */
+    private boolean toLocalMotionEvent(View view, MotionEvent event) {
+        final int[] loc = mTmpLocation;
+        view.getLocationOnScreen(loc);
+        event.offsetLocation(-loc[0], -loc[1]);
+        return true;
+    }
+
+    /**
+     * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
+     * (scaleX, scaleY, etc).
+     */
+    private boolean toGlobalMotionEvent(View view, MotionEvent event) {
+        final int[] loc = mTmpLocation;
+        view.getLocationOnScreen(loc);
+        event.offsetLocation(loc[0], loc[1]);
+        return true;
+    }
+
+    private class DisallowIntercept implements Runnable {
+        @Override
+        public void run() {
+            final ViewParent parent = mSrc.getParent();
+            parent.requestDisallowInterceptTouchEvent(true);
+        }
+    }
+
+    private class TriggerLongPress implements Runnable {
+        @Override
+        public void run() {
+            onLongPress();
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java b/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java
new file mode 100644
index 0000000..1d97400
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/GrowingArrayUtils.java
@@ -0,0 +1,194 @@
+/*
+ * 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.widget;
+
+import java.lang.reflect.Array;
+
+/**
+ * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
+ * arrays. Common array operations are implemented for efficient use in dynamic containers.
+ *
+ * All methods in this class assume that the length of an array is equivalent to its capacity and
+ * NOT the number of elements in the array. The current size of the array is always passed in as a
+ * parameter.
+ */
+class GrowingArrayUtils {
+
+    /**
+     * Appends an element to the end of the array, growing the array if there is no more room.
+     * @param array The array to which to append the element. This must NOT be null.
+     * @param currentSize The number of elements in the array. Must be less than or equal to
+     *                    array.length.
+     * @param element The element to append.
+     * @return the array to which the element was appended. This may be different than the given
+     *         array.
+     */
+    public static <T> T[] append(T[] array, int currentSize, T element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 > array.length) {
+            T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+                    growSize(currentSize));
+            System.arraycopy(array, 0, newArray, 0, currentSize);
+            array = newArray;
+        }
+        array[currentSize] = element;
+        return array;
+    }
+
+    /**
+     * Primitive int version of {@link #append(Object[], int, Object)}.
+     */
+    public static int[] append(int[] array, int currentSize, int element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 > array.length) {
+            int[] newArray = new int[growSize(currentSize)];
+            System.arraycopy(array, 0, newArray, 0, currentSize);
+            array = newArray;
+        }
+        array[currentSize] = element;
+        return array;
+    }
+
+    /**
+     * Primitive long version of {@link #append(Object[], int, Object)}.
+     */
+    public static long[] append(long[] array, int currentSize, long element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 > array.length) {
+            long[] newArray = new long[growSize(currentSize)];
+            System.arraycopy(array, 0, newArray, 0, currentSize);
+            array = newArray;
+        }
+        array[currentSize] = element;
+        return array;
+    }
+
+    /**
+     * Primitive boolean version of {@link #append(Object[], int, Object)}.
+     */
+    public static boolean[] append(boolean[] array, int currentSize, boolean element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 > array.length) {
+            boolean[] newArray = new boolean[growSize(currentSize)];
+            System.arraycopy(array, 0, newArray, 0, currentSize);
+            array = newArray;
+        }
+        array[currentSize] = element;
+        return array;
+    }
+
+    /**
+     * Inserts an element into the array at the specified index, growing the array if there is no
+     * more room.
+     *
+     * @param array The array to which to append the element. Must NOT be null.
+     * @param currentSize The number of elements in the array. Must be less than or equal to
+     *                    array.length.
+     * @param element The element to insert.
+     * @return the array to which the element was appended. This may be different than the given
+     *         array.
+     */
+    public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 <= array.length) {
+            System.arraycopy(array, index, array, index + 1, currentSize - index);
+            array[index] = element;
+            return array;
+        }
+
+        T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+                growSize(currentSize));
+        System.arraycopy(array, 0, newArray, 0, index);
+        newArray[index] = element;
+        System.arraycopy(array, index, newArray, index + 1, array.length - index);
+        return newArray;
+    }
+
+    /**
+     * Primitive int version of {@link #insert(Object[], int, int, Object)}.
+     */
+    public static int[] insert(int[] array, int currentSize, int index, int element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 <= array.length) {
+            System.arraycopy(array, index, array, index + 1, currentSize - index);
+            array[index] = element;
+            return array;
+        }
+
+        int[] newArray = new int[growSize(currentSize)];
+        System.arraycopy(array, 0, newArray, 0, index);
+        newArray[index] = element;
+        System.arraycopy(array, index, newArray, index + 1, array.length - index);
+        return newArray;
+    }
+
+    /**
+     * Primitive long version of {@link #insert(Object[], int, int, Object)}.
+     */
+    public static long[] insert(long[] array, int currentSize, int index, long element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 <= array.length) {
+            System.arraycopy(array, index, array, index + 1, currentSize - index);
+            array[index] = element;
+            return array;
+        }
+
+        long[] newArray = new long[growSize(currentSize)];
+        System.arraycopy(array, 0, newArray, 0, index);
+        newArray[index] = element;
+        System.arraycopy(array, index, newArray, index + 1, array.length - index);
+        return newArray;
+    }
+
+    /**
+     * Primitive boolean version of {@link #insert(Object[], int, int, Object)}.
+     */
+    public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
+        assert currentSize <= array.length;
+
+        if (currentSize + 1 <= array.length) {
+            System.arraycopy(array, index, array, index + 1, currentSize - index);
+            array[index] = element;
+            return array;
+        }
+
+        boolean[] newArray = new boolean[growSize(currentSize)];
+        System.arraycopy(array, 0, newArray, 0, index);
+        newArray[index] = element;
+        System.arraycopy(array, index, newArray, index + 1, array.length - index);
+        return newArray;
+    }
+
+    /**
+     * Given the current size of an array, returns an ideal size to which the array should grow.
+     * This is typically double the given size, but should not be relied upon to do so in the
+     * future.
+     */
+    public static int growSize(int currentSize) {
+        return currentSize <= 4 ? 8 : currentSize * 2;
+    }
+
+    // Uninstantiable
+    private GrowingArrayUtils() {}
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index ce36d87..c7c8289 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -23,14 +23,15 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
-import android.os.SystemClock;
-import android.support.v4.text.TextUtilsCompat;
-import android.support.v4.view.MotionEventCompat;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.os.BuildCompat;
 import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewPropertyAnimatorCompat;
-import android.support.v4.widget.ListViewAutoScrollHelper;
 import android.support.v4.widget.PopupWindowCompat;
 import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -39,19 +40,18 @@
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowManager;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.PopupWindow;
 
 import java.lang.reflect.Method;
-import java.util.Locale;
 
 /**
  * Static library support version of the framework's {@link android.widget.ListPopupWindow}.
@@ -62,7 +62,7 @@
  *
  * @see android.widget.ListPopupWindow
  */
-public class ListPopupWindow {
+public class ListPopupWindow implements ShowableListMenu {
     private static final String TAG = "ListPopupWindow";
     private static final boolean DEBUG = false;
 
@@ -75,6 +75,7 @@
 
     private static Method sClipToWindowEnabledMethod;
     private static Method sGetMaxAvailableHeightMethod;
+    private static Method sSetEpicenterBoundsMethod;
 
     static {
         try {
@@ -90,10 +91,15 @@
             Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
                     + " on PopupWindow. Oh well.");
         }
+        try {
+            sSetEpicenterBoundsMethod = PopupWindow.class.getDeclaredMethod(
+                    "setEpicenterBounds", Rect.class);
+        } catch (NoSuchMethodException e) {
+            Log.i(TAG, "Could not find method setEpicenterBounds(Rect) on PopupWindow. Oh well.");
+        }
     }
 
     private Context mContext;
-    private PopupWindow mPopup;
     private ListAdapter mAdapter;
     private DropDownListView mDropDownList;
 
@@ -103,6 +109,7 @@
     private int mDropDownVerticalOffset;
     private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
     private boolean mDropDownVerticalOffsetSet;
+    private boolean mIsAnimatedFromAnchor = true;
 
     private int mDropDownGravity = Gravity.NO_GRAVITY;
 
@@ -120,7 +127,7 @@
     private Drawable mDropDownListHighlight;
 
     private AdapterView.OnItemClickListener mItemClickListener;
-    private AdapterView.OnItemSelectedListener mItemSelectedListener;
+    private OnItemSelectedListener mItemSelectedListener;
 
     private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
     private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
@@ -130,11 +137,17 @@
 
     private final Handler mHandler;
 
-    private Rect mTempRect = new Rect();
+    private final Rect mTempRect = new Rect();
+
+    /**
+     * Optional anchor-relative bounds to be used as the transition epicenter.
+     * When {@code null}, the anchor bounds are used as the epicenter.
+     */
+    private Rect mEpicenterBounds;
 
     private boolean mModal;
 
-    private int mLayoutDirection;
+    PopupWindow mPopup;
 
     /**
      * The provided prompt view should appear above list content.
@@ -197,7 +210,7 @@
      *
      * @param context Context used for contained views.
      */
-    public ListPopupWindow(Context context) {
+    public ListPopupWindow(@NonNull Context context) {
         this(context, null, R.attr.listPopupWindowStyle);
     }
 
@@ -208,7 +221,7 @@
      * @param context Context used for contained views.
      * @param attrs   Attributes from inflating parent views used to style the popup.
      */
-    public ListPopupWindow(Context context, AttributeSet attrs) {
+    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, R.attr.listPopupWindowStyle);
     }
 
@@ -220,7 +233,8 @@
      * @param attrs Attributes from inflating parent views used to style the popup.
      * @param defStyleAttr Default style attribute to use for popup content.
      */
-    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
@@ -233,7 +247,8 @@
      * @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) {
+    public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
         mContext = context;
         mHandler = new Handler(context.getMainLooper());
 
@@ -250,10 +265,6 @@
 
         mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-
-        // Set the default layout direction to match the default locale one
-        final Locale locale = mContext.getResources().getConfiguration().locale;
-        mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
     }
 
     /**
@@ -262,7 +273,7 @@
      *
      * @param adapter The adapter to use to create this window's content.
      */
-    public void setAdapter(ListAdapter adapter) {
+    public void setAdapter(@Nullable ListAdapter adapter) {
         if (mObserver == null) {
             mObserver = new PopupDataSetObserver();
         } else if (mAdapter != null) {
@@ -395,7 +406,7 @@
     /**
      * @return The background drawable for the popup window.
      */
-    public Drawable getBackground() {
+    public @Nullable Drawable getBackground() {
         return mPopup.getBackground();
     }
 
@@ -404,7 +415,7 @@
      *
      * @param d A drawable to set as the background.
      */
-    public void setBackgroundDrawable(Drawable d) {
+    public void setBackgroundDrawable(@Nullable Drawable d) {
         mPopup.setBackgroundDrawable(d);
     }
 
@@ -413,16 +424,17 @@
      *
      * @param animationStyle Animation style to use.
      */
-    public void setAnimationStyle(int animationStyle) {
+    public void setAnimationStyle(@StyleRes int animationStyle) {
         mPopup.setAnimationStyle(animationStyle);
     }
 
     /**
-     * Returns the animation style that will be used when the popup window is shown or dismissed.
+     * Returns the animation style that will be used when the popup window is
+     * shown or dismissed.
      *
      * @return Animation style that will be used.
      */
-    public int getAnimationStyle() {
+    public @StyleRes int getAnimationStyle() {
         return mPopup.getAnimationStyle();
     }
 
@@ -431,17 +443,17 @@
      *
      * @return The popup's anchor view
      */
-    public View getAnchorView() {
+    public @Nullable View getAnchorView() {
         return mDropDownAnchorView;
     }
 
     /**
-     * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
-     * view when shown.
+     * Sets the popup's anchor view. This popup will always be positioned relative to
+     * the anchor view when shown.
      *
      * @param anchor The view to use as an anchor.
      */
-    public void setAnchorView(View anchor) {
+    public void setAnchorView(@Nullable View anchor) {
         mDropDownAnchorView = anchor;
     }
 
@@ -482,6 +494,17 @@
     }
 
     /**
+     * Specifies the anchor-relative bounds of the popup's transition
+     * epicenter.
+     *
+     * @param bounds anchor-relative bounds
+     * @hide
+     */
+    public void setEpicenterBounds(Rect bounds) {
+        mEpicenterBounds = bounds;
+    }
+
+    /**
      * Set the gravity of the dropdown list. This is commonly used to
      * set gravity to START or END for alignment with the anchor.
      *
@@ -560,7 +583,7 @@
      *
      * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
      */
-    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
+    public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
         mItemClickListener = clickListener;
     }
 
@@ -569,9 +592,9 @@
      *
      * @param selectedListener Listener to register.
      *
-     * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+     * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
      */
-    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
+    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
         mItemSelectedListener = selectedListener;
     }
 
@@ -581,7 +604,7 @@
      *
      * @param prompt View to use as an informational prompt.
      */
-    public void setPromptView(View prompt) {
+    public void setPromptView(@Nullable View prompt) {
         boolean showing = isShowing();
         if (showing) {
             removePromptView();
@@ -603,10 +626,11 @@
      * Show the popup list. If the list is already showing, this method
      * will recalculate the popup's size and position.
      */
+    @Override
     public void show() {
         int height = buildDropDown();
 
-        boolean noInputMethod = isInputMethodNotNeeded();
+        final boolean noInputMethod = isInputMethodNotNeeded();
         PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
 
         if (mPopup.isShowing()) {
@@ -677,6 +701,13 @@
             // only set this if the dropdown is not always visible
             mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
             mPopup.setTouchInterceptor(mTouchInterceptor);
+            if (sSetEpicenterBoundsMethod != null) {
+                try {
+                    sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
+                } catch (Exception e) {
+                    Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
+                }
+            }
             PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
                     mDropDownVerticalOffset, mDropDownGravity);
             mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -693,6 +724,7 @@
     /**
      * Dismiss the popup window.
      */
+    @Override
     public void dismiss() {
         mPopup.dismiss();
         removePromptView();
@@ -706,7 +738,7 @@
      *
      * @param listener Listener that will be notified when the popup is dismissed.
      */
-    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
+    public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
         mPopup.setOnDismissListener(listener);
     }
 
@@ -754,7 +786,7 @@
     public void setSelection(int position) {
         DropDownListView list = mDropDownList;
         if (isShowing() && list != null) {
-            list.mListSelectionHidden = false;
+            list.setListSelectionHidden(false);
             list.setSelection(position);
 
             if (Build.VERSION.SDK_INT >= 11) {
@@ -773,7 +805,7 @@
         final DropDownListView list = mDropDownList;
         if (list != null) {
             // WARNING: Please read the comment where mListSelectionHidden is declared
-            list.mListSelectionHidden = true;
+            list.setListSelectionHidden(true);
             //list.hideSelector();
             list.requestLayout();
         }
@@ -782,6 +814,7 @@
     /**
      * @return {@code true} if the popup is currently showing, {@code false} otherwise.
      */
+    @Override
     public boolean isShowing() {
         return mPopup.isShowing();
     }
@@ -817,7 +850,7 @@
     /**
      * @return The currently selected item or null if the popup is not showing.
      */
-    public Object getSelectedItem() {
+    public @Nullable Object getSelectedItem() {
         if (!isShowing()) {
             return null;
         }
@@ -856,7 +889,7 @@
      *
      * @see ListView#getSelectedView()
      */
-    public View getSelectedView() {
+    public @Nullable View getSelectedView() {
         if (!isShowing()) {
             return null;
         }
@@ -867,10 +900,15 @@
      * @return The {@link ListView} displayed within the popup window.
      * Only valid when {@link #isShowing()} == {@code true}.
      */
-    public ListView getListView() {
+    @Override
+    public @Nullable ListView getListView() {
         return mDropDownList;
     }
 
+    @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+        return new DropDownListView(context, hijackFocus);
+    }
+
     /**
      * The maximum number of list items that can be visible and still have
      * the list expand when touched.
@@ -891,7 +929,7 @@
      *
      * @see #setModal(boolean)
      */
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
+    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
         // when the drop down is shown, we drive it directly
         if (isShowing()) {
             // the key events are forwarded to the list in the drop down view
@@ -932,7 +970,7 @@
                 } else {
                     // WARNING: Please read the comment where mListSelectionHidden
                     //          is declared
-                    mDropDownList.mListSelectionHidden = false;
+                    mDropDownList.setListSelectionHidden(false);
                 }
 
                 consumed = mDropDownList.onKeyDown(keyCode, event);
@@ -986,7 +1024,7 @@
      *
      * @see #setModal(boolean)
      */
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
+    public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
         if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
             boolean consumed = mDropDownList.onKeyUp(keyCode, event);
             if (consumed && isConfirmKey(keyCode)) {
@@ -1010,7 +1048,7 @@
      *
      * @see #setModal(boolean)
      */
-    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
             // special case for the back key, we do not even try to send it
             // to the drop down list but instead, consume it immediately
@@ -1093,7 +1131,7 @@
                 }
             };
 
-            mDropDownList = new DropDownListView(context, !mModal);
+            mDropDownList = createDropDownListView(context, !mModal);
             if (mDropDownListHighlight != null) {
                 mDropDownList.setSelector(mDropDownListHighlight);
             }
@@ -1101,7 +1139,7 @@
             mDropDownList.setOnItemClickListener(mItemClickListener);
             mDropDownList.setFocusable(true);
             mDropDownList.setFocusableInTouchMode(true);
-            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            mDropDownList.setOnItemSelectedListener(new OnItemSelectedListener() {
                 public void onItemSelected(AdapterView<?> parent, View view,
                         int position, long id) {
 
@@ -1109,7 +1147,7 @@
                         DropDownListView dropDownList = mDropDownList;
 
                         if (dropDownList != null) {
-                            dropDownList.mListSelectionHidden = false;
+                            dropDownList.setListSelectionHidden(false);
                         }
                     }
                 }
@@ -1187,20 +1225,21 @@
         }
 
         // getMaxAvailableHeight() subtracts the padding, so we put it back
-        // to get the available height for the whole window
-        int padding = 0;
-        Drawable background = mPopup.getBackground();
+        // to get the available height for the whole window.
+        final int padding;
+        final Drawable background = mPopup.getBackground();
         if (background != null) {
             background.getPadding(mTempRect);
             padding = mTempRect.top + mTempRect.bottom;
 
-            // If we don't have an explicit vertical offset, determine one from the window
-            // background so that content will line up.
+            // If we don't have an explicit vertical offset, determine one from
+            // the window background so that content will line up.
             if (!mDropDownVerticalOffsetSet) {
                 mDropDownVerticalOffset = -mTempRect.top;
             }
         } else {
             mTempRect.setEmpty();
+            padding = 0;
         }
 
         // Max height available on the screen for a popup.
@@ -1216,14 +1255,14 @@
         switch (mDropDownWidth) {
             case ViewGroup.LayoutParams.WRAP_CONTENT:
                 childWidthSpec = MeasureSpec.makeMeasureSpec(
-                        mContext.getResources().getDisplayMetrics().widthPixels -
-                                (mTempRect.left + mTempRect.right),
+                        mContext.getResources().getDisplayMetrics().widthPixels
+                                - (mTempRect.left + mTempRect.right),
                         MeasureSpec.AT_MOST);
                 break;
             case ViewGroup.LayoutParams.MATCH_PARENT:
                 childWidthSpec = MeasureSpec.makeMeasureSpec(
-                        mContext.getResources().getDisplayMetrics().widthPixels -
-                                (mTempRect.left + mTempRect.right),
+                        mContext.getResources().getDisplayMetrics().widthPixels
+                                - (mTempRect.left + mTempRect.right),
                         MeasureSpec.EXACTLY);
                 break;
             default:
@@ -1231,538 +1270,19 @@
                 break;
         }
 
+        // Add padding only if the list has items in it, that way we don't show
+        // the popup if it is not needed.
         final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
                 0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
-        // add padding only if the list has items in it, that way we don't show
-        // the popup if it is not needed
-        if (listContent > 0) otherHeights += padding;
+        if (listContent > 0) {
+            final int listPadding = mDropDownList.getPaddingTop()
+                    + mDropDownList.getPaddingBottom();
+            otherHeights += padding + listPadding;
+        }
 
         return listContent + otherHeights;
     }
 
-    /**
-     * Abstract class that forwards touch events to a {@link ListPopupWindow}.
-     *
-     * @hide
-     */
-    public static abstract class ForwardingListener implements View.OnTouchListener {
-        /** Scaled touch slop, used for detecting movement outside bounds. */
-        private final float mScaledTouchSlop;
-
-        /** Timeout before disallowing intercept on the source's parent. */
-        private final int mTapTimeout;
-        /** Timeout before accepting a long-press to start forwarding. */
-        private final int mLongPressTimeout;
-
-        /** Source view from which events are forwarded. */
-        private final View mSrc;
-
-        /** Runnable used to prevent conflicts with scrolling parents. */
-        private Runnable mDisallowIntercept;
-        /** Runnable used to trigger forwarding on long-press. */
-        private Runnable mTriggerLongPress;
-
-        /** Whether this listener is currently forwarding touch events. */
-        private boolean mForwarding;
-        /**
-         * Whether forwarding was initiated by a long-press. If so, we won't
-         * force the window to dismiss when the touch stream ends.
-         */
-        private boolean mWasLongPress;
-
-        /** The id of the first pointer down in the current event stream. */
-        private int mActivePointerId;
-
-        /**
-         * Temporary Matrix instance
-         */
-        private final int[] mTmpLocation = new int[2];
-
-        public ForwardingListener(View src) {
-            mSrc = src;
-            mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
-            mTapTimeout = ViewConfiguration.getTapTimeout();
-            // Use a medium-press timeout. Halfway between tap and long-press.
-            mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
-        }
-
-        /**
-         * Returns the popup to which this listener is forwarding events.
-         * <p>
-         * Override this to return the correct popup. If the popup is displayed
-         * asynchronously, you may also need to override
-         * {@link #onForwardingStopped} to prevent premature cancelation of
-         * forwarding.
-         *
-         * @return the popup to which this listener is forwarding events
-         */
-        public abstract ListPopupWindow getPopup();
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            final boolean wasForwarding = mForwarding;
-            final boolean forwarding;
-            if (wasForwarding) {
-                if (mWasLongPress) {
-                    // If we started forwarding as a result of a long-press,
-                    // just silently stop forwarding events so that the window
-                    // stays open.
-                    forwarding = onTouchForwarded(event);
-                } else {
-                    forwarding = onTouchForwarded(event) || !onForwardingStopped();
-                }
-            } else {
-                forwarding = onTouchObserved(event) && onForwardingStarted();
-
-                if (forwarding) {
-                    // Make sure we cancel any ongoing source event stream.
-                    final long now = SystemClock.uptimeMillis();
-                    final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
-                            0.0f, 0.0f, 0);
-                    mSrc.onTouchEvent(e);
-                    e.recycle();
-                }
-            }
-
-            mForwarding = forwarding;
-            return forwarding || wasForwarding;
-        }
-
-        /**
-         * Called when forwarding would like to start. <p> By default, this will show the popup
-         * returned by {@link #getPopup()}. It may be overridden to perform another action, like
-         * clicking the source view or preparing the popup before showing it.
-         *
-         * @return true to start forwarding, false otherwise
-         */
-        protected boolean onForwardingStarted() {
-            final ListPopupWindow popup = getPopup();
-            if (popup != null && !popup.isShowing()) {
-                popup.show();
-            }
-            return true;
-        }
-
-        /**
-         * Called when forwarding would like to stop. <p> By default, this will dismiss the popup
-         * returned by {@link #getPopup()}. It may be overridden to perform some other action.
-         *
-         * @return true to stop forwarding, false otherwise
-         */
-        protected boolean onForwardingStopped() {
-            final ListPopupWindow popup = getPopup();
-            if (popup != null && popup.isShowing()) {
-                popup.dismiss();
-            }
-            return true;
-        }
-
-        /**
-         * Observes motion events and determines when to start forwarding.
-         *
-         * @param srcEvent motion event in source view coordinates
-         * @return true to start forwarding motion events, false otherwise
-         */
-        private boolean onTouchObserved(MotionEvent srcEvent) {
-            final View src = mSrc;
-            if (!src.isEnabled()) {
-                return false;
-            }
-
-            final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
-            switch (actionMasked) {
-                case MotionEvent.ACTION_DOWN:
-                    mActivePointerId = srcEvent.getPointerId(0);
-                    mWasLongPress = false;
-
-                    if (mDisallowIntercept == null) {
-                        mDisallowIntercept = new DisallowIntercept();
-                    }
-                    src.postDelayed(mDisallowIntercept, mTapTimeout);
-                    if (mTriggerLongPress == null) {
-                        mTriggerLongPress = new TriggerLongPress();
-                    }
-                    src.postDelayed(mTriggerLongPress, mLongPressTimeout);
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
-                    if (activePointerIndex >= 0) {
-                        final float x = srcEvent.getX(activePointerIndex);
-                        final float y = srcEvent.getY(activePointerIndex);
-                        if (!pointInView(src, x, y, mScaledTouchSlop)) {
-                            clearCallbacks();
-
-                            // Don't let the parent intercept our events.
-                            src.getParent().requestDisallowInterceptTouchEvent(true);
-                            return true;
-                        }
-                    }
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    clearCallbacks();
-                    break;
-            }
-
-            return false;
-        }
-
-        private void clearCallbacks() {
-            if (mTriggerLongPress != null) {
-                mSrc.removeCallbacks(mTriggerLongPress);
-            }
-
-            if (mDisallowIntercept != null) {
-                mSrc.removeCallbacks(mDisallowIntercept);
-            }
-        }
-
-        private void onLongPress() {
-            clearCallbacks();
-
-            final View src = mSrc;
-            if (!src.isEnabled() || src.isLongClickable()) {
-                // Ignore long-press if the view is disabled or has its own
-                // handler.
-                return;
-            }
-
-            if (!onForwardingStarted()) {
-                return;
-            }
-
-            // Don't let the parent intercept our events.
-            src.getParent().requestDisallowInterceptTouchEvent(true);
-
-            // Make sure we cancel any ongoing source event stream.
-            final long now = SystemClock.uptimeMillis();
-            final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
-            src.onTouchEvent(e);
-            e.recycle();
-
-            mForwarding = true;
-            mWasLongPress = true;
-        }
-
-        /**
-         * Handled forwarded motion events and determines when to stop forwarding.
-         *
-         * @param srcEvent motion event in source view coordinates
-         * @return true to continue forwarding motion events, false to cancel
-         */
-        private boolean onTouchForwarded(MotionEvent srcEvent) {
-            final View src = mSrc;
-            final ListPopupWindow popup = getPopup();
-            if (popup == null || !popup.isShowing()) {
-                return false;
-            }
-
-            final DropDownListView dst = popup.mDropDownList;
-            if (dst == null || !dst.isShown()) {
-                return false;
-            }
-
-            // Convert event to destination-local coordinates.
-            final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
-            toGlobalMotionEvent(src, dstEvent);
-            toLocalMotionEvent(dst, dstEvent);
-
-            // Forward converted event to destination view, then recycle it.
-            final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
-            dstEvent.recycle();
-
-            // Always cancel forwarding when the touch stream ends.
-            final int action = MotionEventCompat.getActionMasked(srcEvent);
-            final boolean keepForwarding = action != MotionEvent.ACTION_UP
-                    && action != MotionEvent.ACTION_CANCEL;
-
-            return handled && keepForwarding;
-        }
-
-        private static boolean pointInView(View view, float localX, float localY, float slop) {
-            return localX >= -slop && localY >= -slop &&
-                    localX < ((view.getRight() - view.getLeft()) + slop) &&
-                    localY < ((view.getBottom() - view.getTop()) + slop);
-        }
-
-        /**
-         * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
-         * (scaleX, scaleY, etc).
-         */
-        private boolean toLocalMotionEvent(View view, MotionEvent event) {
-            final int[] loc = mTmpLocation;
-            view.getLocationOnScreen(loc);
-            event.offsetLocation(-loc[0], -loc[1]);
-            return true;
-        }
-
-        /**
-         * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
-         * (scaleX, scaleY, etc).
-         */
-        private boolean toGlobalMotionEvent(View view, MotionEvent event) {
-            final int[] loc = mTmpLocation;
-            view.getLocationOnScreen(loc);
-            event.offsetLocation(loc[0], loc[1]);
-            return true;
-        }
-
-        private class DisallowIntercept implements Runnable {
-            @Override
-            public void run() {
-                final ViewParent parent = mSrc.getParent();
-                parent.requestDisallowInterceptTouchEvent(true);
-            }
-        }
-
-        private class TriggerLongPress implements Runnable {
-            @Override
-            public void run() {
-                onLongPress();
-            }
-        }
-    }
-
-    /**
-     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
-     * make sure the list uses the appropriate drawables and states when
-     * displayed on screen within a drop down. The focus is never actually
-     * passed to the drop down in this mode; the list only looks focused.</p>
-     */
-    private static class DropDownListView extends ListViewCompat {
-
-        /*
-        * WARNING: This is a workaround for a touch mode issue.
-        *
-        * Touch mode is propagated lazily to windows. This causes problems in
-        * the following scenario:
-        * - Type something in the AutoCompleteTextView and get some results
-        * - Move down with the d-pad to select an item in the list
-        * - Move up with the d-pad until the selection disappears
-        * - Type more text in the AutoCompleteTextView *using the soft keyboard*
-        *   and get new results; you are now in touch mode
-        * - The selection comes back on the first item in the list, even though
-        *   the list is supposed to be in touch mode
-        *
-        * Using the soft keyboard triggers the touch mode change but that change
-        * is propagated to our window only after the first list layout, therefore
-        * after the list attempts to resurrect the selection.
-        *
-        * The trick to work around this issue is to pretend the list is in touch
-        * mode when we know that the selection should not appear, that is when
-        * we know the user moved the selection away from the list.
-        *
-        * This boolean is set to true whenever we explicitly hide the list's
-        * selection and reset to false whenever we know the user moved the
-        * selection back to the list.
-        *
-        * When this boolean is true, isInTouchMode() returns true, otherwise it
-        * returns super.isInTouchMode().
-        */
-        private boolean mListSelectionHidden;
-
-        /**
-         * True if this wrapper should fake focus.
-         */
-        private boolean mHijackFocus;
-
-        /** Whether to force drawing of the pressed state selector. */
-        private boolean mDrawsInPressedState;
-
-        /** Current drag-to-open click animation, if any. */
-        private ViewPropertyAnimatorCompat mClickAnimation;
-
-        /** Helper for drag-to-open auto scrolling. */
-        private ListViewAutoScrollHelper mScrollHelper;
-
-        /**
-         * <p>Creates a new list view wrapper.</p>
-         *
-         * @param context this view's context
-         */
-        public DropDownListView(Context context, boolean hijackFocus) {
-            super(context, null, R.attr.dropDownListViewStyle);
-            mHijackFocus = hijackFocus;
-            setCacheColorHint(0); // Transparent, since the background drawable could be anything.
-        }
-
-        /**
-         * Handles forwarded events.
-         *
-         * @param activePointerId id of the pointer that activated forwarding
-         * @return whether the event was handled
-         */
-        public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
-            boolean handledEvent = true;
-            boolean clearPressedItem = false;
-
-            final int actionMasked = MotionEventCompat.getActionMasked(event);
-            switch (actionMasked) {
-                case MotionEvent.ACTION_CANCEL:
-                    handledEvent = false;
-                    break;
-                case MotionEvent.ACTION_UP:
-                    handledEvent = false;
-                    // $FALL-THROUGH$
-                case MotionEvent.ACTION_MOVE:
-                    final int activeIndex = event.findPointerIndex(activePointerId);
-                    if (activeIndex < 0) {
-                        handledEvent = false;
-                        break;
-                    }
-
-                    final int x = (int) event.getX(activeIndex);
-                    final int y = (int) event.getY(activeIndex);
-                    final int position = pointToPosition(x, y);
-                    if (position == INVALID_POSITION) {
-                        clearPressedItem = true;
-                        break;
-                    }
-
-                    final View child = getChildAt(position - getFirstVisiblePosition());
-                    setPressedItem(child, position, x, y);
-                    handledEvent = true;
-
-                    if (actionMasked == MotionEvent.ACTION_UP) {
-                        clickPressedItem(child, position);
-                    }
-                    break;
-            }
-
-            // Failure to handle the event cancels forwarding.
-            if (!handledEvent || clearPressedItem) {
-                clearPressedItem();
-            }
-
-            // Manage automatic scrolling.
-            if (handledEvent) {
-                if (mScrollHelper == null) {
-                    mScrollHelper = new ListViewAutoScrollHelper(this);
-                }
-                mScrollHelper.setEnabled(true);
-                mScrollHelper.onTouch(this, event);
-            } else if (mScrollHelper != null) {
-                mScrollHelper.setEnabled(false);
-            }
-
-            return handledEvent;
-        }
-
-        /**
-         * Starts an alpha animation on the selector. When the animation ends,
-         * the list performs a click on the item.
-         */
-        private void clickPressedItem(final View child, final int position) {
-            final long id = getItemIdAtPosition(position);
-            performItemClick(child, position, id);
-        }
-
-        private void clearPressedItem() {
-            mDrawsInPressedState = false;
-            setPressed(false);
-            // This will call through to updateSelectorState()
-            drawableStateChanged();
-
-            final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
-            if (motionView != null) {
-                motionView.setPressed(false);
-            }
-
-            if (mClickAnimation != null) {
-                mClickAnimation.cancel();
-                mClickAnimation = null;
-            }
-        }
-
-        private void setPressedItem(View child, int position, float x, float y) {
-            mDrawsInPressedState = true;
-
-            // Ordering is essential. First, update the container's pressed state.
-            if (Build.VERSION.SDK_INT >= 21) {
-                drawableHotspotChanged(x, y);
-            }
-            if (!isPressed()) {
-                setPressed(true);
-            }
-
-            // Next, run layout to stabilize child positions.
-            layoutChildren();
-
-            // Manage the pressed view based on motion position. This allows us to
-            // play nicely with actual touch and scroll events.
-            if (mMotionPosition != INVALID_POSITION) {
-                final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
-                if (motionView != null && motionView != child && motionView.isPressed()) {
-                    motionView.setPressed(false);
-                }
-            }
-            mMotionPosition = position;
-
-            // Offset for child coordinates.
-            final float childX = x - child.getLeft();
-            final float childY = y - child.getTop();
-            if (Build.VERSION.SDK_INT >= 21) {
-                child.drawableHotspotChanged(childX, childY);
-            }
-            if (!child.isPressed()) {
-                child.setPressed(true);
-            }
-
-            // Ensure that keyboard focus starts from the last touched position.
-            positionSelectorLikeTouchCompat(position, child, x, y);
-
-            // This needs some explanation. We need to disable the selector for this next call
-            // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
-            // will draw the selector and bad things happen.
-            setSelectorEnabled(false);
-
-            // Refresh the drawable state to reflect the new pressed state,
-            // which will also update the selector state.
-            refreshDrawableState();
-        }
-
-        @Override
-        protected boolean touchModeDrawsInPressedStateCompat() {
-            return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
-        }
-
-        @Override
-        public boolean isInTouchMode() {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasWindowFocus() {
-            return mHijackFocus || super.hasWindowFocus();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean isFocused() {
-            return mHijackFocus || super.isFocused();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasFocus() {
-            return mHijackFocus || super.hasFocus();
-        }
-    }
-
     private class PopupDataSetObserver extends DataSetObserver {
         @Override
         public void onChanged() {
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
new file mode 100644
index 0000000..a1e5645
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.support.annotation.NonNull;
+import android.support.v7.view.menu.MenuBuilder;
+import android.view.MenuItem;
+
+/**
+ * An interface notified when a menu item is hovered. Useful for cases when hover should trigger
+ * some behavior at a higher level, like managing the opening and closing of submenus.
+ *
+ * @hide
+ */
+public interface MenuItemHoverListener {
+    /**
+     * Called when hover exits a menu item.
+     * <p>
+     * If hover is moving to another item, this method will be called before
+     * {@link #onItemHoverEnter(MenuBuilder, MenuItem)} for the newly-hovered item.
+     *
+     * @param menu the item's parent menu
+     * @param item the hovered menu item
+     */
+    void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+
+    /**
+     * Called when hover enters a menu item.
+     *
+     * @param menu the item's parent menu
+     * @param item the hovered menu item
+     */
+    void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
new file mode 100644
index 0000000..11de42e
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
@@ -0,0 +1,222 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.PopupWindowCompat;
+import android.support.v7.view.menu.ListMenuItemView;
+import android.support.v7.view.menu.MenuAdapter;
+import android.support.v7.view.menu.MenuBuilder;
+import android.transition.Transition;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A MenuPopupWindow represents the popup window for menu.
+ *
+ * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
+ * behaviors specific to menus,
+ *
+ * @hide
+ */
+public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
+    private static final String TAG = "MenuPopupWindow";
+
+    private static Method sSetTouchModalMethod;
+
+    static {
+        try {
+            sSetTouchModalMethod = PopupWindow.class.getDeclaredMethod(
+                    "setTouchModal", boolean.class);
+        } catch (NoSuchMethodException e) {
+            Log.i(TAG, "Could not find method setTouchModal() on PopupWindow. Oh well.");
+        }
+    }
+
+    private MenuItemHoverListener mHoverListener;
+
+    public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+        MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
+        view.setHoverListener(this);
+        return view;
+    }
+
+    public void setEnterTransition(Object enterTransition) {
+        if (Build.VERSION.SDK_INT >= 23) {
+            mPopup.setEnterTransition((Transition) enterTransition);
+        }
+    }
+
+    public void setExitTransition(Object exitTransition) {
+        if (Build.VERSION.SDK_INT >= 23) {
+            mPopup.setExitTransition((Transition) exitTransition);
+        }
+    }
+
+    public void setHoverListener(MenuItemHoverListener hoverListener) {
+        mHoverListener = hoverListener;
+    }
+
+    /**
+     * Set whether this window is touch modal or if outside touches will be sent to
+     * other windows behind it.
+     */
+    public void setTouchModal(final boolean touchModal) {
+        if (sSetTouchModalMethod != null) {
+            try {
+                sSetTouchModalMethod.invoke(mPopup, touchModal);
+            } catch (Exception e) {
+                Log.i(TAG, "Could not invoke setTouchModal() on PopupWindow. Oh well.");
+            }
+        }
+    }
+
+    @Override
+    public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+        // Forward up the chain
+        if (mHoverListener != null) {
+            mHoverListener.onItemHoverEnter(menu, item);
+        }
+    }
+
+    @Override
+    public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+        // Forward up the chain
+        if (mHoverListener != null) {
+            mHoverListener.onItemHoverExit(menu, item);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class MenuDropDownListView extends DropDownListView {
+        final int mAdvanceKey;
+        final int mRetreatKey;
+
+        private MenuItemHoverListener mHoverListener;
+        private MenuItem mHoveredMenuItem;
+
+        public MenuDropDownListView(Context context, boolean hijackFocus) {
+            super(context, hijackFocus);
+
+            if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+                mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
+                mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+                mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        }
+
+        public void setHoverListener(MenuItemHoverListener hoverListener) {
+            mHoverListener = hoverListener;
+        }
+
+        public void clearSelection() {
+            setSelection(INVALID_POSITION);
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
+            if (selectedItem != null && keyCode == mAdvanceKey) {
+                if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
+                    performItemClick(
+                            selectedItem,
+                            getSelectedItemPosition(),
+                            getSelectedItemId());
+                }
+                return true;
+            } else if (selectedItem != null && keyCode == mRetreatKey) {
+                setSelection(INVALID_POSITION);
+
+                // Close only the top-level menu.
+                ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onHoverEvent(MotionEvent ev) {
+            // Dispatch any changes in hovered item index to the listener.
+            if (mHoverListener != null) {
+                // The adapter may be wrapped. Adjust the index if necessary.
+                final int headersCount;
+                final MenuAdapter menuAdapter;
+                final ListAdapter adapter = getAdapter();
+                if (adapter instanceof HeaderViewListAdapter) {
+                    final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
+                    headersCount = headerAdapter.getHeadersCount();
+                    menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+                } else {
+                    headersCount = 0;
+                    menuAdapter = (MenuAdapter) adapter;
+                }
+
+                // Find the menu item for the view at the event coordinates.
+                MenuItem menuItem = null;
+                if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
+                    final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
+                    if (position != INVALID_POSITION) {
+                        final int itemPosition = position - headersCount;
+                        if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
+                            menuItem = menuAdapter.getItem(itemPosition);
+                        }
+                    }
+                }
+
+                final MenuItem oldMenuItem = mHoveredMenuItem;
+                if (oldMenuItem != menuItem) {
+                    final MenuBuilder menu = menuAdapter.getAdapterMenu();
+                    if (oldMenuItem != null) {
+                        mHoverListener.onItemHoverExit(menu, oldMenuItem);
+                    }
+
+                    mHoveredMenuItem = menuItem;
+
+                    if (menuItem != null) {
+                        mHoverListener.onItemHoverEnter(menu, menuItem);
+                    }
+                }
+            }
+
+            return super.onHoverEvent(ev);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 7f097c7..83e8f65 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -16,20 +16,19 @@
 
 package android.support.v7.widget;
 
-
 import android.content.Context;
 import android.support.annotation.MenuRes;
 import android.support.v7.appcompat.R;
 import android.support.v7.view.SupportMenuInflater;
 import android.support.v7.view.menu.MenuBuilder;
 import android.support.v7.view.menu.MenuPopupHelper;
-import android.support.v7.view.menu.MenuPresenter;
-import android.support.v7.view.menu.SubMenuBuilder;
+import android.support.v7.view.menu.ShowableListMenu;
 import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.PopupWindow;
 
 /**
  * Static library support version of the framework's {@link android.widget.PopupMenu}.
@@ -38,33 +37,23 @@
  * to switch to the framework's implementation. See the framework SDK
  * documentation for a class overview.
  */
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
-    private Context mContext;
-    private MenuBuilder mMenu;
-    private View mAnchor;
-    private MenuPopupHelper mPopup;
+public class PopupMenu {
+    private final Context mContext;
+    private final MenuBuilder mMenu;
+    private final View mAnchor;
+    private final MenuPopupHelper mPopup;
+
     private OnMenuItemClickListener mMenuItemClickListener;
-    private OnDismissListener mDismissListener;
+    private OnDismissListener mOnDismissListener;
     private View.OnTouchListener mDragListener;
 
     /**
-     * Callback interface used to notify the application that the menu has closed.
-     */
-    public interface OnDismissListener {
-        /**
-         * Called when the associated menu has been dismissed.
+     * Constructor to create a new popup menu with an anchor view.
          *
-         * @param menu The PopupMenu that was dismissed.
-         */
-        public void onDismiss(PopupMenu menu);
-    }
-
-    /**
-     * Construct a new PopupMenu.
-     *
-     * @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 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.
      */
     public PopupMenu(Context context, View anchor) {
         this(context, anchor, Gravity.NO_GRAVITY);
@@ -105,12 +94,33 @@
     public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
             int popupStyleRes) {
         mContext = context;
-        mMenu = new MenuBuilder(context);
-        mMenu.setCallback(this);
         mAnchor = anchor;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(new MenuBuilder.Callback() {
+            @Override
+            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+                if (mMenuItemClickListener != null) {
+                    return mMenuItemClickListener.onMenuItemClick(item);
+                }
+                return false;
+            }
+
+            @Override
+            public void onMenuModeChange(MenuBuilder menu) {
+            }
+        });
+
         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
-        mPopup.setCallback(this);
+        mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+            @Override
+            public void onDismiss() {
+                if (mOnDismissListener != null) {
+                    mOnDismissListener.onDismiss(PopupMenu.this);
+                }
+            }
+        });
     }
 
     /**
@@ -120,7 +130,6 @@
      * the next time the popup is shown.
      *
      * @param gravity the gravity used to align the popup window
-     *
      * @see #getGravity()
      */
     public void setGravity(int gravity) {
@@ -129,7 +138,6 @@
 
     /**
      * @return the gravity used to align the popup window to its anchor view
-     *
      * @see #setGravity(int)
      */
     public int getGravity() {
@@ -137,12 +145,12 @@
     }
 
     /**
-     * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view
+     * Returns an {@link View.OnTouchListener} that can be added to the anchor view
      * to implement drag-to-open behavior.
      * <p>
      * When the listener is set on a view, touching that view and dragging
-     * outside of its bounds will open the popup window. Lifting will select the
-     * currently touched list item.
+     * outside of its bounds will open the popup window. Lifting will select
+     * the currently touched list item.
      * <p>
      * Example usage:
      * <pre>
@@ -154,7 +162,7 @@
      */
     public View.OnTouchListener getDragToOpenListener() {
         if (mDragListener == null) {
-            mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) {
+            mDragListener = new ForwardingListener(mAnchor) {
                 @Override
                 protected boolean onForwardingStarted() {
                     show();
@@ -168,7 +176,7 @@
                 }
 
                 @Override
-                public ListPopupWindow getPopup() {
+                public ShowableListMenu getPopup() {
                     // This will be null until show() is called.
                     return mPopup.getPopup();
                 }
@@ -179,9 +187,10 @@
     }
 
     /**
-     * @return the {@link Menu} associated with this popup. Populate the returned Menu with
-     * items before calling {@link #show()}.
+     * Returns the {@link Menu} associated with this popup. Populate the
+     * returned Menu with items before calling {@link #show()}.
      *
+     * @return the {@link Menu} associated with this popup
      * @see #show()
      * @see #getMenuInflater()
      */
@@ -190,9 +199,8 @@
     }
 
     /**
-     * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
-     * menu returned by {@link #getMenu()}.
-     *
+     * @return a {@link MenuInflater} that can be used to inflate menu items
+     *         from XML into the menu returned by {@link #getMenu()}
      * @see #getMenu()
      */
     public MenuInflater getMenuInflater() {
@@ -200,8 +208,9 @@
     }
 
     /**
-     * Inflate a menu resource into this PopupMenu. This is equivalent to calling
-     * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+     * Inflate a menu resource into this PopupMenu. This is equivalent to
+     * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+     *
      * @param menuRes Menu resource to inflate
      */
     public void inflate(@MenuRes int menuRes) {
@@ -210,6 +219,7 @@
 
     /**
      * Show the menu popup anchored to the view specified during construction.
+     *
      * @see #dismiss()
      */
     public void show() {
@@ -218,6 +228,7 @@
 
     /**
      * Dismiss the menu popup.
+     *
      * @see #show()
      */
     public void dismiss() {
@@ -225,81 +236,49 @@
     }
 
     /**
-     * Set a listener that will be notified when the user selects an item from the menu.
+     * Sets a listener that will be notified when the user selects an item from
+     * the menu.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
         mMenuItemClickListener = listener;
     }
 
     /**
-     * Set a listener that will be notified when this menu is dismissed.
+     * Sets a listener that will be notified when this menu is dismissed.
      *
-     * @param listener Listener to notify
+     * @param listener the listener to notify
      */
     public void setOnDismissListener(OnDismissListener listener) {
-        mDismissListener = listener;
+        mOnDismissListener = listener;
     }
 
     /**
-     * @hide
-     */
-    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-        if (mMenuItemClickListener != null) {
-            return mMenuItemClickListener.onMenuItemClick(item);
-        }
-        return false;
-    }
-
-    /**
-     * @hide
-     */
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        if (mDismissListener != null) {
-            mDismissListener.onDismiss(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public boolean onOpenSubMenu(MenuBuilder subMenu) {
-        if (subMenu == null) return false;
-
-        if (!subMenu.hasVisibleItems()) {
-            return true;
-        }
-
-        // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
-        new MenuPopupHelper(mContext, subMenu, mAnchor).show();
-        return true;
-    }
-
-    /**
-     * @hide
-     */
-    public void onCloseSubMenu(SubMenuBuilder menu) {
-    }
-
-    /**
-     * @hide
-     */
-    public void onMenuModeChange(MenuBuilder menu) {
-    }
-
-    /**
-     * Interface responsible for receiving menu item click events if the items themselves
-     * do not have individual item click listeners.
+     * Interface responsible for receiving menu item click events if the items
+     * themselves do not have individual item click listeners.
      */
     public interface OnMenuItemClickListener {
-        /**
-         * This method will be invoked when a menu item is clicked if the item itself did
-         * not already handle the event.
+    /**
+         * This method will be invoked when a menu item is clicked if the item
+         * itself did not already handle the event.
          *
-         * @param item {@link MenuItem} that was clicked
-         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
-         */
-        public boolean onMenuItemClick(MenuItem item);
+         * @param item the menu item that was clicked
+         * @return {@code true} if the event was handled, {@code false}
+         *         otherwise
+     */
+        boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Callback interface used to notify the application that the menu has closed.
+     */
+    public interface OnDismissListener {
+    /**
+         * Called when the associated menu has been dismissed.
+         *
+         * @param menu the popup menu that was dismissed
+     */
+        void onDismiss(PopupMenu menu);
     }
 }
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index 10e60c59..ea77f2a 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -38,6 +38,7 @@
 import android.os.Parcelable;
 import android.os.ResultReceiver;
 import android.speech.RecognizerIntent;
+import android.support.v4.content.res.ConfigurationHelper;
 import android.support.v4.view.KeyEventCompat;
 import android.support.v4.widget.CursorAdapter;
 import android.support.v7.appcompat.R;
@@ -50,7 +51,9 @@
 import android.text.TextWatcher;
 import android.text.style.ImageSpan;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -1706,6 +1709,14 @@
             mThreshold = getThreshold();
         }
 
+        @Override
+        protected void onFinishInflate() {
+            super.onFinishInflate();
+            DisplayMetrics metrics = getResources().getDisplayMetrics();
+            setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    getSearchViewTextMinWidthDp(), metrics));
+        }
+
         void setSearchView(SearchView searchView) {
             mSearchView = searchView;
         }
@@ -1801,6 +1812,23 @@
             }
             return super.onKeyPreIme(keyCode, event);
         }
+
+        /**
+         * Get minimum width of the search view text entry area.
+         */
+        private int getSearchViewTextMinWidthDp() {
+            final Configuration config = getResources().getConfiguration();
+            final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
+            final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
+
+            if (widthDp >= 960 && heightDp >= 720
+                    && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                return 256;
+            } else if (widthDp >= 600 || (widthDp >= 640 && heightDp >= 480)) {
+                return 192;
+            }
+            return 160;
+        }
     }
 
     private static class AutoCompleteTextViewReflector {
@@ -1879,4 +1907,4 @@
             imm.showSoftInput(view, flags);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
index 1c0151b..02ee49a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
+++ b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.support.v4.graphics.ColorUtils;
 import android.util.TypedValue;
@@ -61,7 +60,7 @@
 
     public static int getThemeAttrColor(Context context, int attr) {
         TEMP_ARRAY[0] = attr;
-        TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
         try {
             return a.getColor(0, 0);
         } finally {
@@ -71,7 +70,7 @@
 
     public static ColorStateList getThemeAttrColorStateList(Context context, int attr) {
         TEMP_ARRAY[0] = attr;
-        TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
         try {
             return a.getColorStateList(0);
         } finally {
diff --git a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
index 50e1046..78b3d94 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 
@@ -36,16 +37,21 @@
     private final Context mContext;
     private final TypedArray mWrapped;
 
+    private TypedValue mTypedValue;
+
     public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
             int[] attrs) {
-        TypedArray array = context.obtainStyledAttributes(set, attrs);
-        return new TintTypedArray(context, array);
+        return new TintTypedArray(context, context.obtainStyledAttributes(set, attrs));
     }
 
     public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
             int[] attrs, int defStyleAttr, int defStyleRes) {
-        TypedArray array = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
-        return new TintTypedArray(context, array);
+        return new TintTypedArray(context,
+                context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes));
+    }
+
+    public static TintTypedArray obtainStyledAttributes(Context context, int resid, int[] attrs) {
+        return new TintTypedArray(context, context.obtainStyledAttributes(resid, attrs));
     }
 
     private TintTypedArray(Context context, TypedArray array) {
@@ -118,6 +124,16 @@
     }
 
     public ColorStateList getColorStateList(int index) {
+        if (mWrapped.hasValue(index)) {
+            final int resourceId = mWrapped.getResourceId(index, 0);
+            if (resourceId != 0) {
+                final ColorStateList value =
+                        ColorStateListUtils.getColorStateList(mContext, resourceId);
+                if (value != null) {
+                    return value;
+                }
+            }
+        }
         return mWrapped.getColorStateList(index);
     }
 
@@ -162,7 +178,15 @@
     }
 
     public int getType(int index) {
-        return mWrapped.getType(index);
+        if (Build.VERSION.SDK_INT >= 21) {
+            return mWrapped.getType(index);
+        } else {
+            if (mTypedValue == null) {
+                mTypedValue = new TypedValue();
+            }
+            mWrapped.getValue(index, mTypedValue);
+            return mTypedValue.type;
+        }
     }
 
     public boolean hasValue(int index) {
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index 9976858..bc0ef79 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -105,6 +105,32 @@
  * <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
  * toolbars than on their application icon. The use of application icon plus title as a standard
  * layout is discouraged on API 21 devices and newer.</p>
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_buttonGravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetLeft
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetRight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_android_gravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logo
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logoDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_maxButtonHeight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_popupTheme
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitle
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextColor
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_title
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextColor
  */
 public class Toolbar extends ViewGroup {
     private static final String TAG = "Toolbar";
@@ -206,9 +232,15 @@
         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 = Gravity.TOP;
-        mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
-                a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
+        mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+
+        // First read the correct attribute
+        int titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargin, 0);
+        if (a.hasValue(R.styleable.Toolbar_titleMargins)) {
+            // Now read the deprecated attribute, if it has a value
+            titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, titleMargin);
+        }
+        mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = titleMargin;
 
         final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
         if (marginStart >= 0) {
@@ -263,6 +295,7 @@
         if (!TextUtils.isEmpty(subtitle)) {
             setSubtitle(subtitle);
         }
+
         // Set the default context, since setPopupTheme() may be a no-op.
         mPopupContext = getContext();
         setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
@@ -325,6 +358,116 @@
         return mPopupTheme;
     }
 
+    /**
+     * Sets the title margin.
+     *
+     * @param start the starting title margin in pixels
+     * @param top the top title margin in pixels
+     * @param end the ending title margin in pixels
+     * @param bottom the bottom title margin in pixels
+     * @see #getTitleMarginStart()
+     * @see #getTitleMarginTop()
+     * @see #getTitleMarginEnd()
+     * @see #getTitleMarginBottom()
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+     */
+    public void setTitleMargin(int start, int top, int end, int bottom) {
+        mTitleMarginStart = start;
+        mTitleMarginTop = top;
+        mTitleMarginEnd = end;
+        mTitleMarginBottom = bottom;
+
+        requestLayout();
+    }
+
+    /**
+     * @return the starting title margin in pixels
+     * @see #setTitleMarginStart(int)
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+     */
+    public int getTitleMarginStart() {
+        return mTitleMarginStart;
+    }
+
+    /**
+     * Sets the starting title margin in pixels.
+     *
+     * @param margin the starting title margin in pixels
+     * @see #getTitleMarginStart()
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+     */
+    public void setTitleMarginStart(int margin) {
+        mTitleMarginStart = margin;
+
+        requestLayout();
+    }
+
+    /**
+     * @return the top title margin in pixels
+     * @see #setTitleMarginTop(int)
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+     */
+    public int getTitleMarginTop() {
+        return mTitleMarginTop;
+    }
+
+    /**
+     * Sets the top title margin in pixels.
+     *
+     * @param margin the top title margin in pixels
+     * @see #getTitleMarginTop()
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+     */
+    public void setTitleMarginTop(int margin) {
+        mTitleMarginTop = margin;
+
+        requestLayout();
+    }
+
+    /**
+     * @return the ending title margin in pixels
+     * @see #setTitleMarginEnd(int)
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+     */
+    public int getTitleMarginEnd() {
+        return mTitleMarginEnd;
+    }
+
+    /**
+     * Sets the ending title margin in pixels.
+     *
+     * @param margin the ending title margin in pixels
+     * @see #getTitleMarginEnd()
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+     */
+    public void setTitleMarginEnd(int margin) {
+        mTitleMarginEnd = margin;
+
+        requestLayout();
+    }
+
+    /**
+     * @return the bottom title margin in pixels
+     * @see #setTitleMarginBottom(int)
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+     */
+    public int getTitleMarginBottom() {
+        return mTitleMarginBottom;
+    }
+
+    /**
+     * Sets the bottom title margin in pixels.
+     *
+     * @param margin the bottom title margin in pixels
+     * @see #getTitleMarginBottom()
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+     */
+    public void setTitleMarginBottom(int margin) {
+        mTitleMarginBottom = margin;
+
+        requestLayout();
+    }
+
     public void onRtlPropertiesChanged(int layoutDirection) {
         if (Build.VERSION.SDK_INT >= 17) {
             super.onRtlPropertiesChanged(layoutDirection);
@@ -721,6 +864,8 @@
      * as screen readers or tooltips.
      *
      * @return The navigation button's content description
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
      */
     @Nullable
     public CharSequence getNavigationContentDescription() {
@@ -734,6 +879,8 @@
      *
      * @param resId Resource ID of a content description string to set, or 0 to
      *              clear the description
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
      */
     public void setNavigationContentDescription(@StringRes int resId) {
         setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
@@ -746,6 +893,8 @@
      *
      * @param description Content description to set, or <code>null</code> to
      *                    clear the content description
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
      */
     public void setNavigationContentDescription(@Nullable CharSequence description) {
         if (!TextUtils.isEmpty(description)) {
@@ -767,6 +916,8 @@
      * tooltips.</p>
      *
      * @param resId Resource ID of a drawable to set
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
      */
     public void setNavigationIcon(@DrawableRes int resId) {
         setNavigationIcon(mDrawableManager.getDrawable(getContext(), resId));
@@ -783,6 +934,8 @@
      * tooltips.</p>
      *
      * @param icon Drawable to set, may be null to clear the icon
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
      */
     public void setNavigationIcon(@Nullable Drawable icon) {
         if (icon != null) {
@@ -803,6 +956,8 @@
      * Return the current drawable used as the navigation icon.
      *
      * @return The navigation icon drawable
+     *
+     * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
      */
     @Nullable
     public Drawable getNavigationIcon() {
diff --git a/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
index 0576c15..cb88cfd 100644
--- a/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
@@ -89,7 +89,7 @@
 
     public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
         this(toolbar, style, R.string.abc_action_bar_up_description,
-                R.drawable.abc_ic_ab_back_mtrl_am_alpha);
+                R.drawable.abc_ic_ab_back_material);
     }
 
     public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index 8dc4ba0..539030d 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -65,9 +65,14 @@
                 android:theme="@style/Theme.AppCompat.Light" />
 
         <activity
+                android:name="android.support.v7.widget.AppCompatSpinnerActivity"
+                android:label="@string/app_compat_spinner_activity"
+                android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity
                 android:name="android.support.v7.widget.AppCompatTextViewActivity"
                 android:label="@string/app_compat_text_view_activity"
-                android:theme="@style/Theme.AppCompat.Light" />
+                android:theme="@style/Theme.TextColors" />
 
         <activity
                 android:name="android.support.v7.widget.AppCompatImageViewActivity"
@@ -75,6 +80,11 @@
                 android:theme="@style/Theme.AppCompat.Light" />
 
         <activity
+                android:name="android.support.v7.widget.AppCompatButtonActivity"
+                android:label="@string/app_compat_button_activity"
+                android:theme="@style/Theme.AppCompat.Light" />
+
+        <activity
                 android:name="android.support.v7.app.LayoutInflaterFactoryTestActivity"/>
 
         <activity
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/tests/res/drawable/test_vector.xml
similarity index 68%
copy from v7/appcompat/res/values-land/config.xml
copy to v7/appcompat/tests/res/drawable/test_vector.xml
index d0d990d..6319a70 100644
--- a/v7/appcompat/res/values-land/config.xml
+++ b/v7/appcompat/tests/res/drawable/test_vector.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true" android:drawable="@drawable/test_vector_on" />
+    <item android:drawable="@drawable/test_vector_off" />
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_vector_off.xml b/v7/appcompat/tests/res/drawable/test_vector_off.xml
new file mode 100644
index 0000000..312dc3a
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_vector_off.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="btn_radio_to_on_mtrl"
+        android:width="32dp"
+        android:viewportWidth="32"
+        android:height="32dp"
+        android:viewportHeight="32">
+    <group
+            android:name="btn_radio_to_on_mtrl_0"
+            android:translateX="16"
+            android:translateY="16" >
+        <group
+                android:name="ring_outer" >
+            <path
+                    android:name="ring_outer_path"
+                    android:strokeColor="#FF000000"
+                    android:strokeWidth="2"
+                    android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+        </group>
+        <group
+                android:name="dot_group"
+                android:scaleX="0"
+                android:scaleY="0" >
+            <path
+                    android:name="dot_path"
+                    android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+                    android:fillColor="#FF000000" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_vector_on.xml b/v7/appcompat/tests/res/drawable/test_vector_on.xml
new file mode 100644
index 0000000..47717c2
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_vector_on.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<vector
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:name="btn_radio_to_off_mtrl"
+        android:width="32dp"
+        android:viewportWidth="32"
+        android:height="32dp"
+        android:viewportHeight="32">
+    <group
+            android:name="btn_radio_to_off_mtrl_0"
+            android:translateX="16"
+            android:translateY="16" >
+        <group
+                android:name="ring_outer" >
+            <path
+                    android:name="ring_outer_path"
+                    android:strokeColor="#FF000000"
+                    android:strokeWidth="2"
+                    android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+        </group>
+        <group
+                android:name="dot_group" >
+            <path
+                    android:name="dot_path"
+                    android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+                    android:fillColor="#FF000000" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
new file mode 100644
index 0000000..72d6500
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.AppCompatButton
+            style="@style/TextStyleAllCapsOn"
+            android:id="@+id/button_caps1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1" />
+
+        <android.support.v7.widget.AppCompatButton
+            style="@style/TextStyleAllCapsOff"
+            android:id="@+id/button_caps2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2" />
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/button_app_allcaps_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            app:textAllCaps="false"/>
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/view_tinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_tinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            android:background="@drawable/test_drawable"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/view_untinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2" />
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/view_untinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            android:background="@drawable/test_background_green" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml b/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml
new file mode 100644
index 0000000..1c0110e
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_tinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_tinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@drawable/test_drawable"
+            android:entries="@array/planets_array"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_untinted_no_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_untinted_background"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            android:background="@drawable/test_background_green" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_magenta_themed_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in"
+            app:popupTheme="@style/MagentaSpinnerPopupTheme" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_unthemed_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            app:backgroundTint="@color/color_state_list_lilac"
+            app:backgroundTintMode="src_in" />
+
+        <android.support.v7.widget.AppCompatSpinner
+            android:id="@+id/view_ocean_themed_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            android:spinnerMode="dropdown"
+            app:popupTheme="@style/OceanSpinnerPopupTheme" />
+    </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index 3094b9c..9fd3f29 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -40,6 +40,13 @@
             android:text="@string/sample_text2" />
 
         <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/text_view_app_allcaps_false"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            app:textAllCaps="false"/>
+
+        <android.support.v7.widget.AppCompatTextView
             android:id="@+id/view_tinted_no_background"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -69,12 +76,33 @@
             android:text="@string/sample_text2"
             android:background="@drawable/test_background_green" />
 
-        <Button
-            android:id="@+id/button_app_allcaps_false"
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_color_hex"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/sample_text2"
-            app:textAllCaps="false"/>
+            android:textColor="#FF0000" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_color_csl"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            android:textColor="@color/color_state_list_ocean" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_color_primary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_color_secondary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            android:textColor="?android:attr/textColorSecondary" />
 
     </LinearLayout>
 
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml
similarity index 69%
copy from v7/appcompat/res/values-land/config.xml
copy to v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml
index d0d990d..cf7e11a 100644
--- a/v7/appcompat/res/values-land/config.xml
+++ b/v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
+
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent"
+         android:button="@drawable/test_vector"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w720dp/bools.xml b/v7/appcompat/tests/res/layout/popup_window_item.xml
similarity index 63%
rename from v7/appcompat/res/values-w720dp/bools.xml
rename to v7/appcompat/tests/res/layout/popup_window_item.xml
index 05c5aab..7726930 100644
--- a/v7/appcompat/res/values-w720dp/bools.xml
+++ b/v7/appcompat/tests/res/layout/popup_window_item.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!-- 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.
@@ -14,6 +14,10 @@
      limitations under the License.
 -->
 
-<resources>
-    <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/popup_row_height"
+    android:textAppearance="?attr/textAppearanceLargePopupMenu"
+    android:gravity="center_vertical" />
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index e167c9b..91d93ca 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -27,6 +27,13 @@
     <android.support.v7.custom.FitWindowsContentLayout
             android:id="@+id/test_content"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
+            android:layout_height="match_parent">
+
+        <EditText
+                android:id="@+id/editText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"/>
+
+    </android.support.v7.custom.FitWindowsContentLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/values/colors.xml b/v7/appcompat/tests/res/values/colors.xml
index eaf38da..1f6fab2 100644
--- a/v7/appcompat/tests/res/values/colors.xml
+++ b/v7/appcompat/tests/res/values/colors.xml
@@ -20,6 +20,7 @@
     <color name="test_red">#FF6030</color>
     <color name="test_green">#50E080</color>
     <color name="test_blue">#3050CF</color>
+    <color name="test_magenta">#F050F0</color>
 
     <color name="lilac_default">#F080F0</color>
     <color name="lilac_disabled">#F0A0FF</color>
diff --git a/v7/appcompat/tests/res/values/dimens.xml b/v7/appcompat/tests/res/values/dimens.xml
index 6ca983d..16a8d4c 100644
--- a/v7/appcompat/tests/res/values/dimens.xml
+++ b/v7/appcompat/tests/res/values/dimens.xml
@@ -17,4 +17,6 @@
     <dimen name="drawable_small_size">12dip</dimen>
     <dimen name="drawable_medium_size">16dip</dimen>
     <dimen name="drawable_large_size">20dip</dimen>
+
+    <dimen name="popup_row_height">48dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index e094078..510f65a 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -49,8 +49,21 @@
     <string name="alert_dialog_custom_text1">Dialog custom text</string>
     <string name="alert_dialog_custom_text2">Dialog more custom text</string>
 
+    <string name="app_compat_spinner_activity">AppCompat spinner</string>
     <string name="app_compat_text_view_activity">AppCompat text view</string>
     <string name="sample_text1">Sample text 1</string>
     <string name="sample_text2">Sample text 2</string>
     <string name="app_compat_image_view_activity">AppCompat image view</string>
+    <string name="app_compat_button_activity">AppCompat button</string>
+    <string-array name="planets_array">
+        <item>Mercury</item>
+        <item>Venus</item>
+        <item>Earth</item>
+        <item>Mars</item>
+        <item>Jupiter</item>
+        <item>Saturn</item>
+        <item>Uranus</item>
+        <item>Neptune</item>
+        <item>Pluto</item>
+    </string-array>
 </resources>
diff --git a/v7/appcompat/tests/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
index e07aff1..0232a2b 100644
--- a/v7/appcompat/tests/res/values/styles.xml
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -25,6 +25,11 @@
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
+    <style name="Theme.TextColors" parent="@style/Theme.AppCompat.Light">
+        <item name="android:textColorPrimary">#FF0000FF</item>
+        <item name="android:textColorSecondary">@color/color_state_list_sand</item>
+    </style>
+
     <style name="TextStyleAllCapsOn" parent="@android:style/TextAppearance.Medium">
         <item name="textAllCaps">true</item>
     </style>
@@ -34,7 +39,16 @@
     </style>
 
     <style name="MagentaThemeOverlay">
-        <item name="colorAccent">#FF00FF</item>
+        <item name="colorAccent">@color/test_magenta</item>
     </style>
 
+    <style name="MagentaSpinnerPopupTheme">
+        <item name="android:background">@color/test_magenta</item>
+    </style>
+
+    <style name="OceanSpinnerPopupTheme">
+        <item name="android:background">@color/ocean_default</item>
+    </style>
+
+    <style name="PopupEmptyStyle" />
 </resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
index f53b8e1..97383c3 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
@@ -25,9 +25,12 @@
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MenuItem;
+import android.view.View;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 public abstract class BaseKeyboardShortcutsTestCase<A extends BaseTestActivity>
         extends BaseInstrumentationTestCase<A> {
@@ -61,4 +64,65 @@
         assertEquals("Correct options item selected", selectedItem.getItemId(), expectedId);
     }
 
+    @Test
+    @SmallTest
+    public void testAccessActionBar() throws Throwable {
+        final BaseTestActivity activity = getActivity();
+
+        final View editText = activity.findViewById(R.id.editText);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                editText.requestFocus();
+            }
+        });
+
+        getInstrumentation().waitForIdleSync();
+        sendControlChar('<');
+        getInstrumentation().waitForIdleSync();
+
+        // Should jump to the action bar after control-<
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertFalse(editText.hasFocus());
+                final View toolbar = activity.findViewById(R.id.toolbar);
+                assertTrue(toolbar.hasFocus());
+            }
+        });
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        getInstrumentation().waitForIdleSync();
+
+        // Should jump to the first view again.
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(editText.hasFocus());
+            }
+        });
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+        getInstrumentation().waitForIdleSync();
+
+        // Now it shouldn't go up to action bar -- it doesn't allow taking focus once left
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(editText.hasFocus());
+            }
+        });
+    }
+
+    private void sendControlChar(char key) throws Throwable {
+        KeyEvent tempEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A);
+        KeyCharacterMap map = tempEvent.getKeyCharacterMap();
+        KeyEvent[] events = map.getEvents(new char[] {key});
+        for (int i = 0; i < events.length; i++) {
+            long time = SystemClock.uptimeMillis();
+            KeyEvent event = events[i];
+            KeyEvent controlKey = new KeyEvent(time, time, event.getAction(), event.getKeyCode(),
+                    event.getRepeatCount(), event.getMetaState() | KeyEvent.META_CTRL_ON);
+            getInstrumentation().sendKeySync(controlKey);
+        }
+    }
+
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
index 7e0b06f..0c098ae 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
@@ -122,6 +122,13 @@
 
     @Test
     @SmallTest
+    public void testRadioButtonInflationWithVectorButton() throws Throwable {
+        testAppCompatWidgetInflation(R.layout.layout_radiobutton_vector,
+                AppCompatRadioButton.class);
+    }
+
+    @Test
+    @SmallTest
     public void testCheckBoxInflation() throws Throwable {
         testAppCompatWidgetInflation(R.layout.layout_checkbox, AppCompatCheckBox.class);
     }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java b/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java
new file mode 100644
index 0000000..09ed7ab
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/MenuBuilderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.view.menu.MenuBuilder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class MenuBuilderTest {
+
+    @Test
+    public void setOptionalIconsVisibleMethodShouldRemainPublic() throws Exception {
+        // This test is to verify workaround for bug in the ROM of Explay Fresh devices with 4.2.2 ROM.
+        // Manufacturer has modified ROM and added a public method setOptionalIconsVisible
+        // to android.view.Menu interface. Because of that the runtime can't load MenuBuilder class
+        // because it had no such public method (it was package local)
+        Method method = MenuBuilder.class
+                .getMethod("setOptionalIconsVisible", boolean.class);
+        assertNotNull(method);
+        assertTrue(Modifier.isPublic(method.getModifiers()));
+    }
+}
+
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTextViewActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTextViewActions.java
deleted file mode 100644
index aa31d67..0000000
--- a/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTextViewActions.java
+++ /dev/null
@@ -1,56 +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.v7.testutils;
-
-import android.support.test.espresso.UiController;
-import android.support.test.espresso.ViewAction;
-import android.support.v7.widget.AppCompatTextView;
-import android.view.View;
-import org.hamcrest.Matcher;
-
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
-import static org.hamcrest.core.AllOf.allOf;
-
-public class AppCompatTextViewActions {
-    /**
-     * Sets text appearance on <code>AppCompatTextView</code>.
-     */
-    public static ViewAction setTextAppearance(final int resId) {
-        return new ViewAction() {
-            @Override
-            public Matcher<View> getConstraints() {
-                return allOf(isDisplayingAtLeast(90), isAssignableFrom(AppCompatTextView.class));
-            }
-
-            @Override
-            public String getDescription() {
-                return "AppCompatTextView set text appearance";
-            }
-
-            @Override
-            public void perform(UiController uiController, View view) {
-                uiController.loopMainThreadUntilIdle();
-
-                AppCompatTextView appCompatTextView = (AppCompatTextView) view;
-                appCompatTextView.setTextAppearance(appCompatTextView.getContext(), resId);
-
-                uiController.loopMainThreadUntilIdle();
-            }
-        };
-    }
-}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
index ff3af31..7ad44bd 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
@@ -46,6 +46,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         final int contentView = getContentViewLayoutResId();
         if (contentView > 0) {
@@ -55,6 +56,12 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
 
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
+
     protected abstract int getContentViewLayoutResId();
 
     protected void onContentViewSet() {
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
index 8bb452e..d258e1f 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
@@ -17,6 +17,9 @@
 
 package android.support.v7.testutils;
 
+import android.support.v4.util.Pair;
+import android.view.View;
+import android.view.ViewParent;
 import junit.framework.Assert;
 
 import android.graphics.Bitmap;
@@ -27,8 +30,79 @@
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class TestUtils {
     /**
+     * This method takes a view and returns a single bitmap that is the layered combination
+     * of background drawables of this view and all its ancestors. It can be used to abstract
+     * away the specific implementation of a view hierarchy that is not exposed via class APIs
+     * or a view hierarchy that depends on the platform version. Instead of hard-coded lookups
+     * of particular inner implementations of such a view hierarchy that can break during
+     * refactoring or on newer platform versions, calling this API returns a "combined" background
+     * of the view.
+     *
+     * For example, it is useful to get the combined background of a popup / dropdown without
+     * delving into the inner implementation details of how that popup is implemented on a
+     * particular platform version.
+     */
+    public static Bitmap getCombinedBackgroundBitmap(View view) {
+        final int bitmapWidth = view.getWidth();
+        final int bitmapHeight = view.getHeight();
+
+        // Create a bitmap
+        final Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
+                Bitmap.Config.ARGB_8888);
+        // Create a canvas that wraps the bitmap
+        final Canvas canvas = new Canvas(bitmap);
+
+        // As the draw pass starts at the top of view hierarchy, our first step is to traverse
+        // the ancestor hierarchy of our view and collect a list of all ancestors with non-null
+        // and visible backgrounds. At each step we're keeping track of the combined offsets
+        // so that we can properly combine all of the visuals together in the next pass.
+        List<View> ancestorsWithBackgrounds = new ArrayList<>();
+        List<Pair<Integer, Integer>> ancestorOffsets = new ArrayList<>();
+        int offsetX = 0;
+        int offsetY = 0;
+        while (true) {
+            final Drawable backgroundDrawable = view.getBackground();
+            if ((backgroundDrawable != null) && backgroundDrawable.isVisible()) {
+                ancestorsWithBackgrounds.add(view);
+                ancestorOffsets.add(Pair.create(offsetX, offsetY));
+            }
+            // Go to the parent
+            ViewParent parent = view.getParent();
+            if (!(parent instanceof View)) {
+                // We're done traversing the ancestor chain
+                break;
+            }
+
+            // Update the offsets based on the location of current view in its parent's bounds
+            offsetX += view.getLeft();
+            offsetY += view.getTop();
+
+            view = (View) parent;
+        }
+
+        // Now we're going to iterate over the collected ancestors in reverse order (starting from
+        // the topmost ancestor) and draw their backgrounds into our combined bitmap. At each step
+        // we are respecting the offsets of our original view in the coordinate system of the
+        // currently drawn ancestor.
+        final int layerCount = ancestorsWithBackgrounds.size();
+        for (int i = layerCount - 1; i >= 0; i--) {
+            View ancestor = ancestorsWithBackgrounds.get(i);
+            Pair<Integer, Integer> offsets = ancestorOffsets.get(i);
+
+            canvas.translate(offsets.first, offsets.second);
+            ancestor.getBackground().draw(canvas);
+            canvas.translate(-offsets.first, -offsets.second);
+        }
+
+        return bitmap;
+    }
+
+    /**
      * Checks whether all the pixels in the specified drawable are of the same specified color.
      *
      * In case there is a color mismatch, the behavior of this method depends on the
@@ -39,60 +113,77 @@
     public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
             int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
             int allowedComponentVariance, boolean throwExceptionIfFails) {
-        // Create a bitmap
-        Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
-        // Create a canvas that wraps the bitmap
-        Canvas canvas = new Canvas(bitmap);
-        if (callSetBounds) {
-            // Configure the drawable to have bounds that match the passed size
-            drawable.setBounds(0, 0, drawableWidth, drawableHeight);
-        }
-        // And ask the drawable to draw itself to the canvas / bitmap
-        drawable.draw(canvas);
+            // Create a bitmap
+            Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight,
+                    Bitmap.Config.ARGB_8888);
+            // Create a canvas that wraps the bitmap
+            Canvas canvas = new Canvas(bitmap);
+            if (callSetBounds) {
+                // Configure the drawable to have bounds that match the passed size
+                drawable.setBounds(0, 0, drawableWidth, drawableHeight);
+            }
+            // And ask the drawable to draw itself to the canvas / bitmap
+            drawable.draw(canvas);
 
         try {
-            int[] rowPixels = new int[drawableWidth];
-            for (int row = 0; row < drawableHeight; row++) {
-                bitmap.getPixels(rowPixels, 0, drawableWidth, 0, row, drawableWidth, 1);
-                for (int column = 0; column < drawableWidth; column++) {
-                    int sourceAlpha = Color.alpha(rowPixels[column]);
-                    int sourceRed = Color.red(rowPixels[column]);
-                    int sourceGreen = Color.green(rowPixels[column]);
-                    int sourceBlue = Color.blue(rowPixels[column]);
+            assertAllPixelsOfColor(failMessagePrefix, bitmap, drawableWidth, drawableHeight, color,
+                    allowedComponentVariance, throwExceptionIfFails);
+        } finally {
+            bitmap.recycle();
+        }
+    }
 
-                    int expectedAlpha = Color.alpha(color);
-                    int expectedRed = Color.red(color);
-                    int expectedGreen = Color.green(color);
-                    int expectedBlue = Color.blue(color);
+    /**
+     * Checks whether all the pixels in the specified bitmap are of the same specified color.
+     *
+     * In case there is a color mismatch, the behavior of this method depends on the
+     * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+     * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+     * <code>Assert.fail</code> with detailed description of the mismatch.
+     */
+    public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Bitmap bitmap,
+            int bitmapWidth, int bitmapHeight, @ColorInt int color,
+            int allowedComponentVariance, boolean throwExceptionIfFails) {
+            int[] rowPixels = new int[bitmapWidth];
+        for (int row = 0; row < bitmapHeight; row++) {
+            bitmap.getPixels(rowPixels, 0, bitmapWidth, 0, row, bitmapWidth, 1);
+            for (int column = 0; column < bitmapWidth; column++) {
+                int sourceAlpha = Color.alpha(rowPixels[column]);
+                int sourceRed = Color.red(rowPixels[column]);
+                int sourceGreen = Color.green(rowPixels[column]);
+                int sourceBlue = Color.blue(rowPixels[column]);
 
-                    int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
-                    int varianceRed = Math.abs(sourceRed - expectedRed);
-                    int varianceGreen = Math.abs(sourceGreen - expectedGreen);
-                    int varianceBlue = Math.abs(sourceBlue - expectedBlue);
+                int expectedAlpha = Color.alpha(color);
+                int expectedRed = Color.red(color);
+                int expectedGreen = Color.green(color);
+                int expectedBlue = Color.blue(color);
 
-                    boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
-                            && (varianceRed <= allowedComponentVariance)
-                            && (varianceGreen <= allowedComponentVariance)
-                            && (varianceBlue <= allowedComponentVariance);
+                int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
+                int varianceRed = Math.abs(sourceRed - expectedRed);
+                int varianceGreen = Math.abs(sourceGreen - expectedGreen);
+                int varianceBlue = Math.abs(sourceBlue - expectedBlue);
 
-                    if (!isColorMatch) {
-                        String mismatchDescription = failMessagePrefix
-                                + ": expected all drawable colors to be ["
-                                + expectedAlpha + "," + expectedRed + ","
-                                + expectedGreen + "," + expectedBlue
-                                + "] but at position (" + row + "," + column + ") found ["
-                                + sourceAlpha + "," + sourceRed + ","
-                                + sourceGreen + "," + sourceBlue + "]";
-                        if (throwExceptionIfFails) {
-                            throw new RuntimeException(mismatchDescription);
-                        } else {
-                            Assert.fail(mismatchDescription);
-                        }
+                boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
+                        && (varianceRed <= allowedComponentVariance)
+                        && (varianceGreen <= allowedComponentVariance)
+                        && (varianceBlue <= allowedComponentVariance);
+
+                if (!isColorMatch) {
+                    String mismatchDescription = failMessagePrefix
+                            + ": expected all drawable colors to be ["
+                            + expectedAlpha + "," + expectedRed + ","
+                            + expectedGreen + "," + expectedBlue
+                            + "] but at position (" + row + "," + column + ") out of ("
+                            + bitmapWidth + "," + bitmapHeight + ") found ["
+                            + sourceAlpha + "," + sourceRed + ","
+                            + sourceGreen + "," + sourceBlue + "]";
+                    if (throwExceptionIfFails) {
+                        throw new RuntimeException(mismatchDescription);
+                    } else {
+                        Assert.fail(mismatchDescription);
                     }
                 }
             }
-        } finally {
-            bitmap.recycle();
         }
     }
 
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
index d17db45..5dab9d1 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
@@ -22,9 +22,13 @@
 import android.support.test.espresso.ViewAction;
 import android.support.v4.view.ViewCompat;
 import android.view.View;
+import android.widget.TextView;
 import org.hamcrest.Matcher;
 
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+import static org.hamcrest.core.AllOf.allOf;
 
 public class TestUtilsActions {
     /**
@@ -54,6 +58,33 @@
     }
 
     /**
+     * Sets text appearance on {@code TextView}.
+     */
+    public static ViewAction setTextAppearance(final int resId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return allOf(isDisplayingAtLeast(90), isAssignableFrom(TextView.class));
+            }
+
+            @Override
+            public String getDescription() {
+                return "TextView set text appearance";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                TextView textView = (TextView) view;
+                textView.setTextAppearance(textView.getContext(), resId);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    /**
      * Sets the passed color state list as the background layer on a {@link View} with
      * {@link ViewCompat#setBackgroundTintList(View, ColorStateList)} API.
      */
@@ -82,7 +113,7 @@
 
     /**
      * Sets the passed mode as the background tint mode on a {@link View} with
-     * {@link ViewCompat#setBackgroundTintList(View, ColorStateList)} API.
+     * {@link ViewCompat#setBackgroundTintMode(View, PorterDuff.Mode)} API.
      */
     public static ViewAction setBackgroundTintModeViewCompat(final PorterDuff.Mode tintMode) {
         return new ViewAction() {
@@ -106,4 +137,27 @@
             }
         };
     }
+
+    public static ViewAction setEnabled(final boolean enabled) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "set enabled";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                view.setEnabled(enabled);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
index da4747d..2c20aa8 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
@@ -17,12 +17,15 @@
 package android.support.v7.testutils;
 
 import android.database.sqlite.SQLiteCursor;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.ColorInt;
 import android.support.test.espresso.matcher.BoundedMatcher;
 import android.support.v4.view.TintableBackgroundView;
 import android.text.TextUtils;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.widget.CheckedTextView;
 import android.widget.ImageView;
 import junit.framework.Assert;
@@ -74,6 +77,79 @@
     }
 
     /**
+     * Returns a matcher that matches <code>View</code>s which have background flat-filled
+     * with the specific color.
+     */
+    public static Matcher isBackground(@ColorInt final int color) {
+        return new BoundedMatcher<View, View>(View.class) {
+            private String failedComparisonDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("with background of color: ");
+
+                description.appendText(failedComparisonDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final View view) {
+                Drawable drawable = view.getBackground();
+                if (drawable == null) {
+                    return false;
+                }
+
+                try {
+                    TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
+                            view.getHeight(), false, color, 0, true);
+                    // If we are here, the color comparison has passed.
+                    failedComparisonDescription = null;
+                    return true;
+                } catch (Throwable t) {
+                    // If we are here, the color comparison has failed.
+                    failedComparisonDescription = t.getMessage();
+                    return false;
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches <code>View</code>s whose combined background starting
+     * from the view and up its ancestor chain matches the specified color.
+     */
+    public static Matcher isCombinedBackground(@ColorInt final int color) {
+        return new BoundedMatcher<View, View>(View.class) {
+            private String failedComparisonDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("with ascendant background of color: ");
+
+                description.appendText(failedComparisonDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                // Create a bitmap with combined backgrounds of the view and its ancestors.
+                Bitmap combinedBackgroundBitmap = TestUtils.getCombinedBackgroundBitmap(view);
+                try {
+                    TestUtils.assertAllPixelsOfColor("", combinedBackgroundBitmap,
+                            combinedBackgroundBitmap.getWidth(),
+                            combinedBackgroundBitmap.getHeight(), color, 0, true);
+                    // If we are here, the color comparison has passed.
+                    failedComparisonDescription = null;
+                    return true;
+                } catch (Throwable t) {
+                    failedComparisonDescription = t.getMessage();
+                    return false;
+                } finally {
+                    combinedBackgroundBitmap.recycle();
+                }
+            }
+        };
+    }
+
+    /**
      * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
      */
     public static Matcher isCheckedTextView() {
@@ -260,4 +336,32 @@
             }
         };
     }
+
+
+    /**
+     * Returns a matcher that matches {@link View}s based on the given child type.
+     *
+     * @param childMatcher the type of the child to match on
+     */
+    public static Matcher<ViewGroup> hasChild(final Matcher<View> childMatcher) {
+        return new TypeSafeMatcher<ViewGroup>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has child: ");
+                childMatcher.describeTo(description);
+            }
+
+            @Override
+            public boolean matchesSafely(ViewGroup view) {
+                final int childCount = view.getChildCount();
+                for (int i = 0; i < childCount; i++) {
+                    if (childMatcher.matches(view.getChildAt(i))) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+    }
+
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
index 48aac20..315c12d 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
@@ -63,6 +63,14 @@
         mResources = activity.getResources();
     }
 
+    /**
+     * Subclasses should override this method to return true if by default the matching
+     * view (such as, say, {@link AppCompatSpinner}) has background set it.
+     */
+    protected boolean hasBackgroundByDefault() {
+        return false;
+    }
+
     private void verifyBackgroundIsColoredAs(String description, @NonNull View view,
             @ColorInt int color, int allowedComponentVariance) {
         Drawable background = view.getBackground();
@@ -78,6 +86,10 @@
     @Test
     @SmallTest
     public void testBackgroundTintingWithNoBackground() {
+        if (hasBackgroundByDefault()) {
+            return;
+        }
+
         final @IdRes int viewId = R.id.view_tinted_no_background;
         final T view = (T) mContainer.findViewById(viewId);
 
@@ -118,6 +130,10 @@
     @Test
     @SmallTest
     public void testBackgroundTintingViewCompatWithNoBackground() {
+        if (hasBackgroundByDefault()) {
+            return;
+        }
+
         final @IdRes int viewId = R.id.view_tinted_no_background;
         final T view = (T) mContainer.findViewById(viewId);
 
@@ -476,7 +492,9 @@
         final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
                 mResources, R.color.lilac_disabled, null);
 
-        assertNull("No background after XML loading", view.getBackground());
+        if (!hasBackgroundByDefault()) {
+            assertNull("No background after XML loading", view.getBackground());
+        }
 
         // Set background on our view
         onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
@@ -540,7 +558,9 @@
         final @ColorInt int backgroundColorRed = ResourcesCompat.getColor(
                 mResources, R.color.test_red, null);
 
-        assertNull("No background after XML loading", view.getBackground());
+        if (!hasBackgroundByDefault()) {
+            assertNull("No background after XML loading", view.getBackground());
+        }
 
         // Set src_over tint mode on our view. As the currently set tint list is using
         // translucent colors, we expect the actual background of the view to be different under
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java
similarity index 64%
copy from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
copy to v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java
index 124d683..0ede862 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java
@@ -13,22 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.support.v7.widget;
 
-package android.support.design.widget;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
 
-import android.support.design.test.R;
-
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+public class AppCompatButtonActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.test_design_snackbar;
+        return R.layout.appcompat_button_activity;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
new file mode 100644
index 0000000..46925ce
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtilsActions;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatButton} class.
+ */
+@SmallTest
+public class AppCompatButtonTest
+        extends AppCompatBaseViewTest<AppCompatButtonActivity, AppCompatButton> {
+    public AppCompatButtonTest() {
+        super(AppCompatButtonActivity.class);
+    }
+
+    @Override
+    protected boolean hasBackgroundByDefault() {
+        // Button has default background set on it
+        return true;
+    }
+
+    @Test
+    public void testAllCaps() {
+        final String text1 = mResources.getString(R.string.sample_text1);
+        final String text2 = mResources.getString(R.string.sample_text2);
+
+        final AppCompatButton button1 =
+                (AppCompatButton) mContainer.findViewById(R.id.button_caps1);
+        final AppCompatButton button2 =
+                (AppCompatButton) mContainer.findViewById(R.id.button_caps2);
+
+        // Note that Button.getText() returns the original text. We are interested in
+        // the transformed text that is set on the Layout object used to draw the final
+        // (transformed) content.
+        assertEquals("Button starts in all caps on", text1.toUpperCase(),
+                button1.getLayout().getText());
+        assertEquals("Button starts in all caps off", text2,
+                button2.getLayout().getText());
+
+        // Toggle all-caps mode on the two buttons. Note that as with the core Button,
+        // setting a style with textAllCaps=false on a AppCompatButton with all-caps on
+        // will have no effect.
+        onView(withId(R.id.button_caps1)).perform(setTextAppearance(R.style.TextStyleAllCapsOff));
+        onView(withId(R.id.button_caps2)).perform(setTextAppearance(R.style.TextStyleAllCapsOn));
+
+        assertEquals("Button is still in all caps on", text1.toUpperCase(),
+                button1.getLayout().getText());
+        assertEquals("Button is in all caps on", text2.toUpperCase(),
+                button2.getLayout().getText());
+    }
+
+    @Test
+    public void testAppCompatAllCapsFalseOnButton() {
+        final String text = mResources.getString(R.string.sample_text2);
+        final AppCompatButton button =
+                (AppCompatButton) mContainer.findViewById(R.id.button_app_allcaps_false);
+
+        assertEquals("Button is not in all caps", text, button.getLayout().getText());
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
index 8d6233d..6d0215f 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
@@ -17,7 +17,7 @@
 
 /**
  * In addition to all tinting-related tests done by the base class, this class provides
- * tests specific to <code>AppCompatImageView</code> class.
+ * tests specific to {@link AppCompatImageView} class.
  */
 public class AppCompatImageViewTest
         extends AppCompatBaseViewTest<AppCompatImageViewActivity, AppCompatImageView> {
diff --git a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java
similarity index 64%
copy from design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
copy to v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java
index 124d683..c994f9c 100644
--- a/design/tests/src/android/support/design/widget/SnackbarBucketTestsActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java
@@ -13,22 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.support.v7.widget;
 
-package android.support.design.widget;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
 
-import android.support.design.test.R;
-
-public class SnackbarBucketTestsActivity extends BaseTestActivity {
-
-    CoordinatorLayout mCoordinatorLayout;
-
+public class AppCompatSpinnerActivity extends BaseTestActivity {
     @Override
     protected int getContentViewLayoutResId() {
-        return R.layout.test_design_snackbar;
+        return R.layout.appcompat_spinner_activity;
     }
-
-    @Override
-    protected void onContentViewSet() {
-        mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.col);
-    }
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
new file mode 100644
index 0000000..1d9ea9f
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 android.content.res.Resources;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.IdRes;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v7.testutils.TestUtilsMatchers.hasChild;
+import static android.support.v7.testutils.TestUtilsMatchers.isCombinedBackground;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatSpinner} class.
+ */
+@SmallTest
+public class AppCompatSpinnerTest
+        extends AppCompatBaseViewTest<AppCompatSpinnerActivity, AppCompatSpinner> {
+    public AppCompatSpinnerTest() {
+        super(AppCompatSpinnerActivity.class);
+    }
+
+    @Override
+    protected boolean hasBackgroundByDefault() {
+        // Spinner has default background set on it
+        return true;
+    }
+
+    /**
+     * Helper method that verifies that the popup for the specified {@link AppCompatSpinner}
+     * is themed with the specified color.
+     */
+    private void verifySpinnerPopupTheming(@IdRes int spinnerId,
+            @ColorRes int expectedPopupColorResId, boolean matchDropDownListView) {
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        final @ColorInt int expectedPopupColor =
+                ResourcesCompat.getColor(res, expectedPopupColorResId, null);
+        final AppCompatSpinner spinner = (AppCompatSpinner) mContainer.findViewById(spinnerId);
+
+        // Click the spinner to show its popup content
+        onView(withId(spinnerId)).perform(click());
+
+        // The internal implementation details of the AppCompatSpinner's popup content depends
+        // on the platform version itself (in android.widget.PopupWindow) as well as on when the
+        // popup theme is being applied first (in XML or at runtime). Instead of trying to catch
+        // all possible variations of how the popup content is wrapped, we use a view matcher that
+        // creates a single bitmap that combines backgrounds starting from the parent of the
+        // popup content items upwards (drawing them in reverse order), and then tests that the
+        // combined bitmap matches the expected color fill. This should remove dependency on the
+        // internal implementation details on which exact "chrome" part of the popup has the
+        // matching background.
+        String itemText = (String) spinner.getAdapter().getItem(2);
+        Matcher popupContentMatcher = hasChild(withText(itemText));
+        onView(popupContentMatcher).inRoot(isPlatformPopup()).check(
+                matches(isCombinedBackground(expectedPopupColor)));
+
+        // Click an entry in the popup to dismiss it
+        onView(withText(itemText)).perform(click());
+    }
+
+    @Test
+    public void testPopupThemingFromXmlAttribute() {
+        verifySpinnerPopupTheming(R.id.view_magenta_themed_popup, R.color.test_magenta, true);
+    }
+
+    @Test
+    public void testUnthemedPopupRuntimeTheming() {
+        final AppCompatSpinner spinner =
+                (AppCompatSpinner) mContainer.findViewById(R.id.view_unthemed_popup);
+        spinner.setPopupBackgroundResource(R.drawable.test_background_blue);
+        verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_blue, false);
+
+        // Set a different popup background
+        spinner.setPopupBackgroundDrawable(ContextCompat.getDrawable(
+                mActivityTestRule.getActivity(), R.drawable.test_background_green));
+        verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_green, false);
+    }
+
+    @Test
+    public void testThemedPopupRuntimeTheming() {
+        final AppCompatSpinner spinner =
+                (AppCompatSpinner) mContainer.findViewById(R.id.view_ocean_themed_popup);
+        verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.ocean_default, true);
+
+        // Now set a different popup background
+        spinner.setPopupBackgroundResource(R.drawable.test_background_red);
+        verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.test_red, false);
+
+        // Set a different popup background
+        spinner.setPopupBackgroundDrawable(ContextCompat.getDrawable(
+                mActivityTestRule.getActivity(), R.drawable.test_background_blue));
+        verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.test_blue, false);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
index 0883032..af91494 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -18,18 +18,24 @@
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
+import static android.support.v7.testutils.TestUtilsActions.setEnabled;
+import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
 import static org.junit.Assert.assertEquals;
 
+import android.graphics.Color;
+import android.support.test.espresso.action.ViewActions;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v7.appcompat.test.R;
-import android.support.v7.testutils.AppCompatTextViewActions;
+import android.support.v7.testutils.TestUtilsActions;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.widget.Button;
+import android.widget.TextView;
 
 import org.junit.Test;
 
 /**
  * In addition to all tinting-related tests done by the base class, this class provides
- * tests specific to <code>AppCompatTextView</code> class.
+ * tests specific to {@link AppCompatTextView} class.
  */
 @SmallTest
 public class AppCompatTextViewTest
@@ -39,7 +45,7 @@
     }
 
     @Test
-    public void testAllCaps() throws Throwable {
+    public void testAllCaps() {
         final String text1 = mResources.getString(R.string.sample_text1);
         final String text2 = mResources.getString(R.string.sample_text2);
 
@@ -60,9 +66,9 @@
         // setting a style with textAllCaps=false on a AppCompatTextView with all-caps on
         // will have no effect.
         onView(withId(R.id.text_view_caps1)).perform(
-                AppCompatTextViewActions.setTextAppearance(R.style.TextStyleAllCapsOff));
+                setTextAppearance(R.style.TextStyleAllCapsOff));
         onView(withId(R.id.text_view_caps2)).perform(
-                AppCompatTextViewActions.setTextAppearance(R.style.TextStyleAllCapsOn));
+                setTextAppearance(R.style.TextStyleAllCapsOn));
 
         assertEquals("Text view is still in all caps on", text1.toUpperCase(),
                 textView1.getLayout().getText());
@@ -71,10 +77,50 @@
     }
 
     @Test
-    public void testAppCompatAllCapsFalseOnButton() throws Throwable {
+    public void testAppCompatAllCapsFalseOnButton() {
         final String text = mResources.getString(R.string.sample_text2);
-        final Button button = (Button) mContainer.findViewById(R.id.button_app_allcaps_false);
+        final AppCompatTextView textView =
+                (AppCompatTextView) mContainer.findViewById(R.id.text_view_app_allcaps_false);
 
-        assertEquals("Button is not in all caps", text, button.getLayout().getText());
+        assertEquals("Text view is not in all caps", text, textView.getLayout().getText());
+    }
+
+    @Test
+    public void testTextColorSetHex() {
+        final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_hex);
+        assertEquals(Color.RED, textView.getCurrentTextColor());
+    }
+
+    @Test
+    public void testTextColorSetColorStateList() {
+        final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_csl);
+
+        onView(withId(R.id.view_text_color_csl)).perform(setEnabled(true));
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.ocean_default),
+                textView.getCurrentTextColor());
+
+        onView(withId(R.id.view_text_color_csl)).perform(setEnabled(false));
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.ocean_disabled),
+                textView.getCurrentTextColor());
+    }
+
+    @Test
+    public void testTextColorSetThemeAttrHex() {
+        final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_primary);
+        assertEquals(Color.BLUE, textView.getCurrentTextColor());
+    }
+
+    @Test
+    public void testTextColorSetThemeAttrColorStateList() {
+        final TextView textView = (TextView)
+                mContainer.findViewById(R.id.view_text_color_secondary);
+
+        onView(withId(R.id.view_text_color_secondary)).perform(setEnabled(true));
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.sand_default),
+                textView.getCurrentTextColor());
+
+        onView(withId(R.id.view_text_color_secondary)).perform(setEnabled(false));
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.sand_disabled),
+                textView.getCurrentTextColor());
     }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
index 7545981..6a5a58a 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -16,11 +16,13 @@
 package android.support.v7.widget;
 
 import android.app.Instrumentation;
+import android.content.Context;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
 import android.support.v7.app.BaseInstrumentationTestCase;
 import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
@@ -297,6 +299,99 @@
     }
 
     /**
+     * Emulates a drag-down gestures by injecting ACTION events with {@link Instrumentation}.
+     */
+    private void emulateDragDownGesture(int emulatedX, int emulatedStartY, int swipeAmount) {
+        // The logic below uses Instrumentation to emulate a swipe / drag gesture to bring up
+        // the popup content. Note that we don't want to use Espresso's GeneralSwipeAction
+        // as that operates on the level of an individual view. Here we want to test correct
+        // forwarding of events that cross the boundary between the anchor and the popup menu.
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Inject DOWN event
+        long downTime = SystemClock.uptimeMillis();
+        MotionEvent eventDown = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_DOWN, emulatedX, emulatedStartY, 1);
+        instrumentation.sendPointerSync(eventDown);
+
+        // Inject a sequence of MOVE events that emulate a "swipe down" gesture
+        for (int i = 0; i < 10; i++) {
+            long moveTime = SystemClock.uptimeMillis();
+            final int moveY = emulatedStartY + swipeAmount * i / 10;
+            MotionEvent eventMove = MotionEvent.obtain(
+                    moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedX, moveY, 1);
+            instrumentation.sendPointerSync(eventMove);
+            // sleep for a bit to emulate a 200ms swipe
+            SystemClock.sleep(20);
+        }
+
+        // Inject UP event
+        long upTime = SystemClock.uptimeMillis();
+        MotionEvent eventUp = MotionEvent.obtain(
+                upTime, upTime, MotionEvent.ACTION_UP, emulatedX, emulatedStartY + swipeAmount, 1);
+        instrumentation.sendPointerSync(eventUp);
+
+        // Wait for the system to process all events in the queue
+        instrumentation.waitForIdleSync();
+    }
+
+    @Test
+    @MediumTest
+    public void testCreateOnDragListener() throws Throwable {
+        // In this test we want precise control over the height of the popup content since
+        // we need to know by how much to swipe down to end the emulated gesture over the
+        // specific item in the popup. This is why we're using a popup style that removes
+        // all decoration around the popup content, as well as our own row layout with known
+        // height.
+        Builder popupBuilder = new Builder()
+                .withPopupStyleAttr(R.style.PopupEmptyStyle)
+                .withContentRowLayoutId(R.layout.popup_window_item)
+                .withItemClickListener().withDismissListener();
+
+        // Configure ListPopupWindow without showing it
+        popupBuilder.configure();
+
+        // Get the anchor view and configure it with ListPopupWindow's drag-to-open listener
+        final View anchor = mActivityTestRule.getActivity().findViewById(R.id.test_button);
+        View.OnTouchListener dragListener = mListPopupWindow.createDragToOpenListener(anchor);
+        anchor.setOnTouchListener(dragListener);
+        // And also configure it to show the popup window on click
+        anchor.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mListPopupWindow.show();
+            }
+        });
+
+        // Get the height of a row item in our popup window
+        final int popupRowHeight = mActivityTestRule.getActivity().getResources()
+                .getDimensionPixelSize(R.dimen.popup_row_height);
+
+        final int[] anchorOnScreenXY = new int[2];
+        anchor.getLocationOnScreen(anchorOnScreenXY);
+
+        // Compute the start coordinates of a downward swipe and the amount of swipe. We'll
+        // be swiping by twice the row height. That, combined with the swipe originating in the
+        // center of the anchor should result in clicking the second row in the popup.
+        int emulatedX = anchorOnScreenXY[0] + anchor.getWidth() / 2;
+        int emulatedStartY = anchorOnScreenXY[1] + anchor.getHeight() / 2;
+        int swipeAmount = 2 * popupRowHeight;
+
+        // Emulate drag-down gesture with a sequence of motion events
+        emulateDragDownGesture(emulatedX, emulatedStartY, swipeAmount);
+
+        // We expect the swipe / drag gesture to result in clicking the second item in our list.
+        verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+                any(AdapterView.class), any(View.class), eq(1), eq(1L));
+        // Since our item click listener calls dismiss() on the popup, we expect the popup to not
+        // be showing
+        assertFalse(mListPopupWindow.isShowing());
+        // At this point our popup should have notified its dismiss listener
+        verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
+    }
+
+    /**
      * Inner helper class to configure an instance of <code>ListPopupWindow</code> for the
      * specific test. The main reason for its existence is that once a popup window is shown
      * with the show() method, most of its configuration APIs are no-ops. This means that
@@ -311,11 +406,27 @@
         private AdapterView.OnItemClickListener mOnItemClickListener;
         private PopupWindow.OnDismissListener mOnDismissListener;
 
+        private int mContentRowLayoutId = R.layout.abc_popup_menu_item_layout;
+
+        private boolean mUseCustomPopupStyle;
+        private int mPopupStyleAttr;
+
         public Builder setModal(boolean isModal) {
             mIsModal = isModal;
             return this;
         }
 
+        public Builder withContentRowLayoutId(int contentRowLayoutId) {
+            mContentRowLayoutId = contentRowLayoutId;
+            return this;
+        }
+
+        public Builder withPopupStyleAttr(int popupStyleAttr) {
+            mUseCustomPopupStyle = true;
+            mPopupStyleAttr = popupStyleAttr;
+            return this;
+        }
+
         public Builder withItemClickListener() {
             mHasItemClickListener = true;
             return this;
@@ -326,8 +437,13 @@
             return this;
         }
 
-        private void show() {
-            mListPopupWindow = new ListPopupWindow(mContainer.getContext());
+        private void configure() {
+            final Context context = mContainer.getContext();
+            if (mUseCustomPopupStyle) {
+                mListPopupWindow = new ListPopupWindow(context, null, mPopupStyleAttr, 0);
+            } else {
+                mListPopupWindow = new ListPopupWindow(context);
+            }
 
             final String[] POPUP_CONTENT =
                     new String[]{"Alice", "Bob", "Charlie", "Deirdre", "El"};
@@ -355,7 +471,7 @@
                 public View getView(int position, View convertView, ViewGroup parent) {
                     if (convertView == null) {
                         convertView = LayoutInflater.from(parent.getContext()).inflate(
-                                R.layout.abc_popup_menu_item_layout, parent, false);
+                                mContentRowLayoutId, parent, false);
                         ViewHolder viewHolder = new ViewHolder();
                         viewHolder.title = (TextView) convertView.findViewById(R.id.title);
                         convertView.setTag(viewHolder);
@@ -390,6 +506,10 @@
             }
 
             mListPopupWindow.setModal(mIsModal);
+        }
+
+        private void show() {
+            configure();
             mListPopupWindow.show();
         }
 
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
index fa1bf8a..1fe9633 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
@@ -63,11 +63,11 @@
 public class PopupMenuTest extends BaseInstrumentationTestCase<PopupTestActivity> {
     // Since PopupMenu doesn't expose any access to the underlying
     // implementation (like ListPopupWindow.getListView), we're relying on the
-    // class name of the list view from ListPopupWindow that is being used
+    // class name of the list view from MenuPopupWindow that is being used
     // in PopupMenu. This is not the cleanest, but it's not making any assumptions
     // on the platform-specific details of the popup windows.
     private static final String DROP_DOWN_CLASS_NAME =
-            "android.support.v7.widget.ListPopupWindow$DropDownListView";
+            "android.support.v7.widget.MenuPopupWindow$MenuDropDownListView";
     private FrameLayout mContainer;
 
     private Button mButton;
@@ -266,7 +266,7 @@
         assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0],
                 popupOnScreenXY[0], 1);
         assertEquals("Anchoring Y", anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight(),
-                popupOnScreenXY[1] + popupPadding.top, 1);
+                popupOnScreenXY[1], 1);
     }
 
     @Test
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index cdb952d..7fa5cec 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -24,6 +24,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -34,6 +35,8 @@
 LOCAL_MODULE := android-support-v7-cardview-base
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, base)
+LOCAL_JAVA_LIBRARIES := android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -44,7 +47,9 @@
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+    android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -55,7 +60,9 @@
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-eclair-mr1
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+    android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -67,7 +74,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
     android-support-v7-cardview-jellybean-mr1
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res  \
+    android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -81,7 +90,9 @@
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-api21
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+    android-support-annotations
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v7/cardview/api/current.txt b/v7/cardview/api/current.txt
index bb62ed7..28c51ad 100644
--- a/v7/cardview/api/current.txt
+++ b/v7/cardview/api/current.txt
@@ -14,6 +14,7 @@
     method public float getRadius();
     method public boolean getUseCompatPadding();
     method public void setCardBackgroundColor(int);
+    method public void setCardBackgroundColor(android.content.res.ColorStateList);
     method public void setCardElevation(float);
     method public void setContentPadding(int, int, int, int);
     method public void setMaxCardElevation(float);
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index 833ccc8..c4af30b 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -16,13 +16,15 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.annotation.Nullable;
 import android.view.View;
 
 class CardViewApi21 implements CardViewImpl {
 
     @Override
-    public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
-            float radius, float elevation, float maxElevation) {
+    public void initialize(CardViewDelegate cardView, Context context,
+            ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
         final RoundRectDrawable backgroundDrawable = new RoundRectDrawable(backgroundColor, radius);
         cardView.setBackgroundDrawable(backgroundDrawable);
         View view = (View) cardView;
@@ -103,7 +105,7 @@
     }
 
     @Override
-    public void setBackgroundColor(CardViewDelegate cardView, int color) {
+    public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList 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 5dba5d6..68fd714 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -27,6 +27,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 
 import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding;
 import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding;
@@ -46,18 +47,25 @@
     private boolean mInsetForPadding = false;
     private boolean mInsetForRadius = true;
 
+    private ColorStateList mBackground;
     private PorterDuffColorFilter mTintFilter;
     private ColorStateList mTint;
     private PorterDuff.Mode mTintMode;
 
-    public RoundRectDrawable(int backgroundColor, float radius) {
+    public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
         mRadius = radius;
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
-        mPaint.setColor(backgroundColor);
+        setBackground(backgroundColor);
+
         mBoundsF = new RectF();
         mBoundsI = new Rect();
     }
 
+    private void setBackground(ColorStateList color) {
+        mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
+        mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
+    }
+
     void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
         if (padding == mPadding && mInsetForPadding == insetForPadding &&
                 mInsetForRadius == insetForRadius) {
@@ -147,8 +155,8 @@
         return mRadius;
     }
 
-    public void setColor(int color) {
-        mPaint.setColor(color);
+    public void setColor(@Nullable ColorStateList color) {
+        setBackground(color);
         invalidateSelf();
     }
 
@@ -168,16 +176,22 @@
 
     @Override
     protected boolean onStateChange(int[] stateSet) {
+        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
+        final boolean colorChanged = newColor != mPaint.getColor();
+        if (colorChanged) {
+            mPaint.setColor(newColor);
+        }
         if (mTint != null && mTintMode != null) {
             mTintFilter = createTintFilter(mTint, mTintMode);
             return true;
         }
-        return false;
+        return colorChanged;
     }
 
     @Override
     public boolean isStateful() {
-        return (mTint != null && mTint.isStateful()) || super.isStateful();
+        return (mTint != null && mTint.isStateful())
+                || (mBackground != null && mBackground.isStateful()) || super.isStateful();
     }
 
     /**
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
index 24b902c..97f2688 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
@@ -16,12 +16,15 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.annotation.Nullable;
+
 /**
  * Interface for platform specific CardView implementations.
  */
 interface CardViewImpl {
-    void initialize(CardViewDelegate cardView, Context context, int backgroundColor, float radius,
-            float elevation, float maxElevation);
+    void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
+            float radius, float elevation, float maxElevation);
 
     void setRadius(CardViewDelegate cardView, float radius);
 
@@ -47,5 +50,5 @@
 
     void onPreventCornerOverlapChanged(CardViewDelegate cardView);
 
-    void setBackgroundColor(CardViewDelegate cardView, int color);
+    void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color);
 }
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index 5f15f66..65ca583 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -2,6 +2,10 @@
 
 archivesBaseName = 'cardview-v7'
 
+dependencies {
+    compile project(':support-annotations')
+}
+
 android {
     // WARNING: should be 7
     compileSdkVersion project.ext.currentSdk
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 680f77c..384dbfd 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -16,10 +16,12 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.support.annotation.Nullable;
 
 class CardViewEclairMr1 implements CardViewImpl {
 
@@ -61,20 +63,21 @@
                     canvas.drawRect(bounds.left + roundedCornerRadius - 1f, bounds.top,
                             bounds.right - roundedCornerRadius + 1f,
                             bounds.top + roundedCornerRadius, paint);
+
                     canvas.drawRect(bounds.left + roundedCornerRadius - 1f,
-                            bounds.bottom - roundedCornerRadius + 1f,
+                            bounds.bottom - roundedCornerRadius,
                             bounds.right - roundedCornerRadius + 1f, bounds.bottom, paint);
                 }
                 // center
-                canvas.drawRect(bounds.left, bounds.top + Math.max(0, cornerRadius - 1f),
-                        bounds.right, bounds.bottom - cornerRadius + 1f, paint);
+                canvas.drawRect(bounds.left, bounds.top + cornerRadius,
+                        bounds.right, bounds.bottom - cornerRadius , paint);
             }
         };
     }
 
     @Override
-    public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
-            float radius, float elevation, float maxElevation) {
+    public void initialize(CardViewDelegate cardView, Context context,
+            ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
         RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
                 elevation, maxElevation);
         background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
@@ -82,7 +85,7 @@
         updatePadding(cardView);
     }
 
-    RoundRectDrawableWithShadow createBackground(Context context, int backgroundColor,
+    RoundRectDrawableWithShadow createBackground(Context context, ColorStateList backgroundColor,
             float radius, float elevation, float maxElevation) {
         return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
                 elevation, maxElevation);
@@ -110,7 +113,7 @@
     }
 
     @Override
-    public void setBackgroundColor(CardViewDelegate cardView, int color) {
+    public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
         getShadowBackground(cardView).setColor(color);
     }
 
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 505edcc..8e0868d 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
@@ -15,8 +15,10 @@
  */
 package android.support.v7.widget;
 
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
@@ -27,6 +29,7 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
 import android.support.v7.cardview.R;
 
 /**
@@ -72,6 +75,8 @@
     // actual value set by developer
     float mRawShadowSize;
 
+    private ColorStateList mBackground;
+
     private boolean mDirty = true;
 
     private final int mShadowStartColor;
@@ -85,13 +90,13 @@
      */
     private boolean mPrintedShadowClipWarning = false;
 
-    RoundRectDrawableWithShadow(Resources resources, int backgroundColor, float radius,
+    RoundRectDrawableWithShadow(Resources resources, ColorStateList backgroundColor, float radius,
             float shadowSize, float maxShadowSize) {
         mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
         mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
         mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
-        mPaint.setColor(backgroundColor);
+        setBackground(backgroundColor);
         mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
         mCornerShadowPaint.setStyle(Paint.Style.FILL);
         mCornerRadius = (int) (radius + .5f);
@@ -101,6 +106,11 @@
         setShadowSize(shadowSize, maxShadowSize);
     }
 
+    private void setBackground(ColorStateList color) {
+        mBackground = (color == null) ?  ColorStateList.valueOf(Color.TRANSPARENT) : color;
+        mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
+    }
+
     /**
      * Casts the value to an even integer.
      */
@@ -187,6 +197,23 @@
     }
 
     @Override
+    protected boolean onStateChange(int[] stateSet) {
+        final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
+        if (mPaint.getColor() == newColor) {
+            return false;
+        }
+        mPaint.setColor(newColor);
+        mDirty = true;
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    public boolean isStateful() {
+        return (mBackground != null && mBackground.isStateful()) || super.isStateful();
+    }
+
+    @Override
     public void setColorFilter(ColorFilter cf) {
         mPaint.setColorFilter(cf);
     }
@@ -350,8 +377,8 @@
         return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
     }
 
-    public void setColor(int color) {
-        mPaint.setColor(color);
+    public void setColor(@Nullable ColorStateList color) {
+        setBackground(color);
         invalidateSelf();
     }
 
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index 30e7e7a..68b262f 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -17,10 +17,12 @@
 package android.support.v7.widget;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Build;
+import android.support.annotation.Nullable;
 import android.support.v7.cardview.R;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
@@ -213,9 +215,9 @@
     private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
                 R.style.CardView);
-        int backgroundColor;
+        ColorStateList backgroundColor;
         if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
-            backgroundColor = a.getColor(R.styleable.CardView_cardBackgroundColor, 0);
+            backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
         } else {
             // There isn't one set, so we'll compute one based on the theme
             final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
@@ -225,9 +227,9 @@
             // If the theme colorBackground is light, use our own light color, otherwise dark
             final float[] hsv = new float[3];
             Color.colorToHSV(themeColorBackground, hsv);
-            backgroundColor = hsv[2] > 0.5f
+            backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
                     ? getResources().getColor(R.color.cardview_light_background)
-                    : getResources().getColor(R.color.cardview_dark_background);
+                    : getResources().getColor(R.color.cardview_dark_background));
         }
         float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
         float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
@@ -288,6 +290,16 @@
      * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
      */
     public void setCardBackgroundColor(int color) {
+        IMPL.setBackgroundColor(this, ColorStateList.valueOf(color));
+    }
+
+    /**
+     * Updates the background ColorStateList of the CardView
+     *
+     * @param color The new ColorStateList to set for the card background
+     * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
+     */
+    public void setCardBackgroundColor(@Nullable ColorStateList color) {
         IMPL.setBackgroundColor(this, color);
     }
 
diff --git a/v7/gridlayout/Android.mk b/v7/gridlayout/Android.mk
index 27ddfbd..4617db6 100644
--- a/v7/gridlayout/Android.mk
+++ b/v7/gridlayout/Android.mk
@@ -24,6 +24,7 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SDK_VERSION := 7
 LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 6e363dd..7cae6c3 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -4,11 +4,23 @@
 
 dependencies {
     compile project(':support-v4')
+
+    androidTestCompile ('com.android.support.test:runner:0.4.1') {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+        exclude module: 'support-annotations'
+    }
+    testCompile 'junit:junit:4.12'
 }
 
 android {
     compileSdkVersion project.ext.currentSdk
 
+    defaultConfig {
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
         main.java.srcDir 'src'
diff --git a/v7/gridlayout/tests/AndroidManifest.xml b/v7/gridlayout/tests/AndroidManifest.xml
index bc50c89..fc502a5 100644
--- a/v7/gridlayout/tests/AndroidManifest.xml
+++ b/v7/gridlayout/tests/AndroidManifest.xml
@@ -1,28 +1,35 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2015 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
+   Copyright (C) 2015 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
           package="android.support.v7.gridlayout.test">
-    <uses-sdk android:minSdkVersion="7"/>
+    <uses-sdk
+            android:minSdkVersion="7"
+            android:targetSdkVersion="23"
+            tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+                  android.support.test.espresso, android.support.test.espresso.idling" />
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.support.v7.widget.test.GridLayoutTestActivity"/>
+
+        <activity android:name="android.support.v7.widget.GridLayoutTestActivity"/>
     </application>
 
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-                     android:targetPackage="android.support.v7.gridlayout.test"/>
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.v7.gridlayout.test"/>
 </manifest>
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java
new file mode 100644
index 0000000..47c49a1
--- /dev/null
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.widget;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.gridlayout.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GridLayoutTest {
+    @Rule public final ActivityTestRule<GridLayoutTestActivity> mActivityTestRule;
+
+    private View mLeftView;
+    private View mRightView;
+    private View mGridView;
+
+    public GridLayoutTest() {
+        mActivityTestRule = new ActivityTestRule<>(GridLayoutTestActivity.class);
+    }
+
+    private void setContentView(final int layoutId) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final Activity activity = mActivityTestRule.getActivity();
+                activity.setContentView(layoutId);
+                // Now that we've set the content view, find the views we'll be testing
+                mLeftView = activity.findViewById(R.id.leftView);
+                mRightView = activity.findViewById(R.id.rightView);
+                mGridView = activity.findViewById(R.id.gridView);
+            }
+        });
+        instrumentation.waitForIdleSync();
+    }
+
+    @Test
+    public void testUseDefaultMargin() {
+        setContentView(R.layout.use_default_margin_test);
+        int left = mLeftView.getWidth();
+        int right = mRightView.getWidth();
+        int total = mGridView.getWidth();
+        assertTrue("left item should get some width", left > 0);
+        assertTrue("right item should get some width", right > 0);
+        assertTrue("test sanity", total > 0);
+        assertTrue("left view should be almost two times right view " + left + " vs " + right,
+                Math.abs(right * 2 - left) < 2);
+    }
+
+    @Test
+    public void testImplicitFillHorizontal() {
+        setContentView(R.layout.fill_horizontal_test);
+        int left = mLeftView.getWidth();
+        int right = mRightView.getWidth();
+        int total = mGridView.getWidth();
+        assertTrue("left item should get some width", left > 0);
+        assertTrue("right item should get some width", right > 0);
+        assertTrue("test sanity", total > 0);
+        assertTrue("left view should be almost two times right view " + left + " vs " + right,
+                Math.abs(right * 2 - left) < 2);
+    }
+
+    @Test
+    public void testMakeViewGone() {
+        setContentView(R.layout.make_view_gone_test);
+        int left = mLeftView.getWidth();
+        int right = mRightView.getWidth();
+        int total = mGridView.getWidth();
+        assertTrue("left item should get some width", left > 0);
+        assertTrue("right item should get some width", right > 0);
+        assertTrue("test sanity", total > 0);
+        // set second view to gone
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final View rightView = mActivityTestRule.getActivity().findViewById(R.id.rightView);
+                GridLayout.LayoutParams lp = (GridLayout.LayoutParams) rightView.getLayoutParams();
+                lp.setGravity(Gravity.NO_GRAVITY);
+                rightView.setVisibility(View.GONE);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        left = mActivityTestRule.getActivity().findViewById(R.id.leftView).getWidth();
+        assertEquals(total, left);
+    }
+
+    @Test
+    public void testWrapContentInOtherDirection() {
+        setContentView(R.layout.height_wrap_content_test);
+        int left = mLeftView.getHeight();
+        int right = mRightView.getHeight();
+        int total = mGridView.getHeight();
+        assertTrue("test sanity", left > 0);
+        assertTrue("test sanity", right > 0);
+        assertTrue("test sanity", total > 0);
+        assertTrue("right should be taller than left", right > left);
+        assertTrue("total height should be smaller than what it could be",
+                total < ((ViewGroup) mGridView.getParent()).getHeight());
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/TestActivity.java b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java
similarity index 70%
rename from v4/tests/java/android/support/v4/widget/TestActivity.java
rename to v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java
index 9ab5188..3196afc 100644
--- a/v4/tests/java/android/support/v4/widget/TestActivity.java
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java
@@ -14,22 +14,21 @@
  * limitations under the License.
  */
 
-package android.support.v4.widget;
+package android.support.v7.widget;
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
 
-public class TestActivity extends Activity {
-    FrameLayout mContainer;
-
+public class GridLayoutTestActivity extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mContainer = new FrameLayout(this);
+        overridePendingTransition(0, 0);
+    }
 
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        setContentView(mContainer);
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
     }
 }
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
deleted file mode 100644
index aa81870..0000000
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.widget.test;
-
-import android.app.Activity;
-import android.os.Debug;
-import android.support.v7.widget.GridLayout;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.support.v7.gridlayout.test.R;
-import android.test.UiThreadTest;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-/**
- * @hide
- */
-@SmallTest
-public class GridLayoutTest extends ActivityInstrumentationTestCase2 {
-
-    public GridLayoutTest() {
-        super("android.support.v7.widget.test", GridLayoutTestActivity.class);
-    }
-
-    private void setContentView(final int layoutId) throws Throwable {
-        final Activity activity = getActivity();
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.setContentView(layoutId);
-            }
-        });
-    }
-
-    public void testUseDefaultMargin() throws Throwable {
-        setContentView(R.layout.use_default_margin_test);
-        getInstrumentation().waitForIdleSync();
-        int left = getActivity().findViewById(R.id.leftView).getWidth();
-        int right = getActivity().findViewById(R.id.rightView).getWidth();
-        int total = getActivity().findViewById(R.id.gridView).getWidth();
-        assertTrue("left item should get some width", left > 0);
-        assertTrue("right item should get some width", right > 0);
-        assertTrue("test sanity", total > 0);
-        assertTrue("left view should be almost two times right view " + left + " vs " + right,
-                Math.abs(right * 2 - left) < 2);
-    }
-
-    public void testImplicitFillHorizontal() throws Throwable {
-        setContentView(R.layout.fill_horizontal_test);
-        getInstrumentation().waitForIdleSync();
-        int left = getActivity().findViewById(R.id.leftView).getWidth();
-        int right = getActivity().findViewById(R.id.rightView).getWidth();
-        int total = getActivity().findViewById(R.id.gridView).getWidth();
-        assertTrue("left item should get some width", left > 0);
-        assertTrue("right item should get some width", right > 0);
-        assertTrue("test sanity", total > 0);
-        assertTrue("left view should be almost two times right view " + left + " vs " + right,
-                Math.abs(right * 2 - left) < 2);
-    }
-
-    public void testMakeViewGone() throws Throwable {
-        setContentView(R.layout.make_view_gone_test);
-        getInstrumentation().waitForIdleSync();
-        int left = getActivity().findViewById(R.id.leftView).getWidth();
-        final int right = getActivity().findViewById(R.id.rightView).getWidth();
-        int total = getActivity().findViewById(R.id.gridView).getWidth();
-        assertTrue("left item should get some width", left > 0);
-        assertTrue("right item should get some width", right > 0);
-        assertTrue("test sanity", total > 0);
-        // set second view to gone
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final View rightView = getActivity().findViewById(R.id.rightView);
-                GridLayout.LayoutParams lp = (GridLayout.LayoutParams) rightView.getLayoutParams();
-                lp.setGravity(Gravity.NO_GRAVITY);
-                rightView.setVisibility(View.GONE);
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        left = getActivity().findViewById(R.id.leftView).getWidth();
-        assertEquals(total, left);
-    }
-    public void testWrapContentInOtherDirection() throws Throwable {
-        setContentView(R.layout.height_wrap_content_test);
-        getInstrumentation().waitForIdleSync();
-        int left = getActivity().findViewById(R.id.leftView).getHeight();
-        final int right = getActivity().findViewById(R.id.rightView).getHeight();
-        final View gridView = getActivity().findViewById(R.id.gridView);
-        int total = gridView.getHeight();
-        assertTrue("test sanity", left > 0);
-        assertTrue("test sanity", right > 0);
-        assertTrue("test sanity", total > 0);
-        assertTrue("right should be taller than left", right > left);
-        assertTrue("total height should be smaller than what it could be",
-                total < ((ViewGroup)gridView.getParent()).getHeight());
-
-    }
-}
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 8af3b34..ada74c5 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -28,6 +28,7 @@
 	--auto-add-overlay \
 	--extra-packages android.support.v7.appcompat
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -37,6 +38,7 @@
 LOCAL_MODULE := android-support-v7-mediarouter-jellybean
 LOCAL_SDK_VERSION := 16
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean)
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -47,6 +49,7 @@
 LOCAL_SDK_VERSION := 17
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -57,6 +60,18 @@
 LOCAL_SDK_VERSION := 18
 LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr1
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# A helper sub-library that makes direct use of V24 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-mediarouter-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -69,10 +84,11 @@
 LOCAL_MODULE := android-support-v7-mediarouter
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-api24
 LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
     android-support-v7-appcompat \
     android-support-v7-palette
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -81,7 +97,19 @@
 # ---------------------------------------------
 support_module := $(LOCAL_MODULE)
 support_module_api_dir := $(LOCAL_PATH)/api
-support_module_src_files := $(LOCAL_SRC_FILES)
+# We're asking doclava to generate stubs for android.support.v7.app in addition
+# to mediarouter, so we'll have to point doclava at the sources for
+# android.support.v7.app. Note that this API definition will overlap with that
+# of the android.support.v7.app package.
+support_module_src_files := $(LOCAL_SRC_FILES) \
+  ../appcompat/src/android/support/v7/app/AppCompatDelegate.java \
+  ../appcompat/src/android/support/v7/app/AppCompatCallback.java \
+  ../appcompat/src/android/support/v7/app/AppCompatDialog.java \
+  ../appcompat/src/android/support/v7/app/AlertDialog.java \
+  ../appcompat/src/android/support/v7/app/ActionBar.java \
+  ../appcompat/src/android/support/v7/app/ActionBarDrawerToggle.java \
+
+
 support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
 support_module_java_packages := android.support.v7.app android.support.v7.media
 include $(SUPPORT_API_CHECK)
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index 8ae7018..e1d6ad6 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -3,46 +3,37 @@
   public abstract class ActionBar {
     ctor public ActionBar();
     method public abstract void addOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
-    method public abstract void addTab(android.support.v7.app.ActionBar.Tab);
-    method public abstract void addTab(android.support.v7.app.ActionBar.Tab, boolean);
-    method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int);
-    method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
-    method public boolean collapseActionView();
-    method public void dispatchMenuVisibilityChanged(boolean);
+    method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab);
+    method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, boolean);
+    method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int);
+    method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
     method public abstract android.view.View getCustomView();
     method public abstract int getDisplayOptions();
     method public float getElevation();
     method public abstract int getHeight();
     method public int getHideOffset();
-    method public abstract int getNavigationItemCount();
-    method public abstract int getNavigationMode();
-    method public abstract int getSelectedNavigationIndex();
-    method public abstract android.support.v7.app.ActionBar.Tab getSelectedTab();
+    method public abstract deprecated int getNavigationItemCount();
+    method public abstract deprecated int getNavigationMode();
+    method public abstract deprecated int getSelectedNavigationIndex();
+    method public abstract deprecated android.support.v7.app.ActionBar.Tab getSelectedTab();
     method public abstract java.lang.CharSequence getSubtitle();
-    method public abstract android.support.v7.app.ActionBar.Tab getTabAt(int);
-    method public abstract int getTabCount();
+    method public abstract deprecated android.support.v7.app.ActionBar.Tab getTabAt(int);
+    method public abstract deprecated int getTabCount();
     method public android.content.Context getThemedContext();
     method public abstract java.lang.CharSequence getTitle();
     method public abstract void hide();
-    method public boolean invalidateOptionsMenu();
     method public boolean isHideOnContentScrollEnabled();
     method public abstract boolean isShowing();
-    method public boolean isTitleTruncated();
-    method public abstract android.support.v7.app.ActionBar.Tab newTab();
-    method public void onConfigurationChanged(android.content.res.Configuration);
-    method public boolean onKeyShortcut(int, android.view.KeyEvent);
-    method public boolean onMenuKeyEvent(android.view.KeyEvent);
-    method public boolean openOptionsMenu();
-    method public abstract void removeAllTabs();
+    method public abstract deprecated android.support.v7.app.ActionBar.Tab newTab();
+    method public abstract deprecated void removeAllTabs();
     method public abstract void removeOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
-    method public abstract void removeTab(android.support.v7.app.ActionBar.Tab);
-    method public abstract void removeTabAt(int);
-    method public abstract void selectTab(android.support.v7.app.ActionBar.Tab);
+    method public abstract deprecated void removeTab(android.support.v7.app.ActionBar.Tab);
+    method public abstract deprecated void removeTabAt(int);
+    method public abstract deprecated void selectTab(android.support.v7.app.ActionBar.Tab);
     method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public abstract void setCustomView(android.view.View);
     method public abstract void setCustomView(android.view.View, android.support.v7.app.ActionBar.LayoutParams);
     method public abstract void setCustomView(int);
-    method public void setDefaultDisplayHomeAsUpEnabled(boolean);
     method public abstract void setDisplayHomeAsUpEnabled(boolean);
     method public abstract void setDisplayOptions(int);
     method public abstract void setDisplayOptions(int, int);
@@ -60,29 +51,26 @@
     method public void setHomeButtonEnabled(boolean);
     method public abstract void setIcon(int);
     method public abstract void setIcon(android.graphics.drawable.Drawable);
-    method public abstract void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
+    method public abstract deprecated void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
     method public abstract void setLogo(int);
     method public abstract void setLogo(android.graphics.drawable.Drawable);
-    method public abstract void setNavigationMode(int);
-    method public abstract void setSelectedNavigationItem(int);
-    method public void setShowHideAnimationEnabled(boolean);
+    method public abstract deprecated void setNavigationMode(int);
+    method public abstract deprecated void setSelectedNavigationItem(int);
     method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
     method public abstract void setSubtitle(java.lang.CharSequence);
     method public abstract void setSubtitle(int);
     method public abstract void setTitle(java.lang.CharSequence);
     method public abstract void setTitle(int);
-    method public void setWindowTitle(java.lang.CharSequence);
     method public abstract void show();
-    method public android.support.v7.view.ActionMode startActionMode(android.support.v7.view.ActionMode.Callback);
     field public static final int DISPLAY_HOME_AS_UP = 4; // 0x4
     field public static final int DISPLAY_SHOW_CUSTOM = 16; // 0x10
     field public static final int DISPLAY_SHOW_HOME = 2; // 0x2
     field public static final int DISPLAY_SHOW_TITLE = 8; // 0x8
     field public static final int DISPLAY_USE_LOGO = 1; // 0x1
-    field public static final int NAVIGATION_MODE_LIST = 1; // 0x1
-    field public static final int NAVIGATION_MODE_STANDARD = 0; // 0x0
-    field public static final int NAVIGATION_MODE_TABS = 2; // 0x2
+    field public static final deprecated int NAVIGATION_MODE_LIST = 1; // 0x1
+    field public static final deprecated int NAVIGATION_MODE_STANDARD = 0; // 0x0
+    field public static final deprecated int NAVIGATION_MODE_TABS = 2; // 0x2
   }
 
   public static class ActionBar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
@@ -99,11 +87,11 @@
     method public abstract void onMenuVisibilityChanged(boolean);
   }
 
-  public static abstract interface ActionBar.OnNavigationListener {
+  public static abstract deprecated interface ActionBar.OnNavigationListener {
     method public abstract boolean onNavigationItemSelected(int, long);
   }
 
-  public static abstract class ActionBar.Tab {
+  public static abstract deprecated class ActionBar.Tab {
     ctor public ActionBar.Tab();
     method public abstract java.lang.CharSequence getContentDescription();
     method public abstract android.view.View getCustomView();
@@ -125,7 +113,7 @@
     field public static final int INVALID_POSITION = -1; // 0xffffffff
   }
 
-  public static abstract interface ActionBar.TabListener {
+  public static abstract deprecated interface ActionBar.TabListener {
     method public abstract void onTabReselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
     method public abstract void onTabSelected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
     method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
@@ -157,6 +145,10 @@
     method public abstract void setActionBarUpIndicator(android.graphics.drawable.Drawable, int);
   }
 
+  public static abstract interface ActionBarDrawerToggle.DelegateProvider {
+    method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+  }
+
   public class AlertDialog extends android.support.v7.app.AppCompatDialog implements android.content.DialogInterface {
     ctor protected AlertDialog(android.content.Context);
     ctor protected AlertDialog(android.content.Context, int);
@@ -174,6 +166,47 @@
     method public void setView(android.view.View, int, int, int, int);
   }
 
+  public static class AlertDialog.Builder {
+    ctor public AlertDialog.Builder(android.content.Context);
+    ctor public AlertDialog.Builder(android.content.Context, int);
+    method public android.support.v7.app.AlertDialog create();
+    method public android.content.Context getContext();
+    method public android.support.v7.app.AlertDialog.Builder setAdapter(android.widget.ListAdapter, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setCancelable(boolean);
+    method public android.support.v7.app.AlertDialog.Builder setCursor(android.database.Cursor, android.content.DialogInterface.OnClickListener, java.lang.String);
+    method public android.support.v7.app.AlertDialog.Builder setCustomTitle(android.view.View);
+    method public android.support.v7.app.AlertDialog.Builder setIcon(int);
+    method public android.support.v7.app.AlertDialog.Builder setIcon(android.graphics.drawable.Drawable);
+    method public android.support.v7.app.AlertDialog.Builder setIconAttribute(int);
+    method public android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
+    method public android.support.v7.app.AlertDialog.Builder setItems(int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setItems(java.lang.CharSequence[], android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setMessage(int);
+    method public android.support.v7.app.AlertDialog.Builder setMessage(java.lang.CharSequence);
+    method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(int, boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(java.lang.CharSequence[], boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(android.database.Cursor, java.lang.String, java.lang.String, android.content.DialogInterface.OnMultiChoiceClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNegativeButton(int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNegativeButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNeutralButton(int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNeutralButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setOnCancelListener(android.content.DialogInterface.OnCancelListener);
+    method public android.support.v7.app.AlertDialog.Builder setOnDismissListener(android.content.DialogInterface.OnDismissListener);
+    method public android.support.v7.app.AlertDialog.Builder setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
+    method public android.support.v7.app.AlertDialog.Builder setOnKeyListener(android.content.DialogInterface.OnKeyListener);
+    method public android.support.v7.app.AlertDialog.Builder setPositiveButton(int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setPositiveButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(int, int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.database.Cursor, int, java.lang.String, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(java.lang.CharSequence[], int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.widget.ListAdapter, int, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setTitle(int);
+    method public android.support.v7.app.AlertDialog.Builder setTitle(java.lang.CharSequence);
+    method public android.support.v7.app.AlertDialog.Builder setView(int);
+    method public android.support.v7.app.AlertDialog.Builder setView(android.view.View);
+    method public android.support.v7.app.AlertDialog show();
+  }
+
   public abstract interface AppCompatCallback {
     method public abstract void onSupportActionModeFinished(android.support.v7.view.ActionMode);
     method public abstract void onSupportActionModeStarted(android.support.v7.view.ActionMode);
@@ -321,6 +354,7 @@
     field public static final java.lang.String ACTION_REMOVE = "android.media.intent.action.REMOVE";
     field public static final java.lang.String ACTION_RESUME = "android.media.intent.action.RESUME";
     field public static final java.lang.String ACTION_SEEK = "android.media.intent.action.SEEK";
+    field public static final java.lang.String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
     field public static final java.lang.String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
     field public static final java.lang.String ACTION_STOP = "android.media.intent.action.STOP";
     field public static final java.lang.String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
@@ -337,6 +371,8 @@
     field public static final java.lang.String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
     field public static final java.lang.String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
     field public static final java.lang.String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+    field public static final java.lang.String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+    field public static final java.lang.String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
     field public static final java.lang.String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
     field public static final java.lang.String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
     field public static final java.lang.String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
@@ -655,6 +691,7 @@
     method public void getSessionStatus(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
     method public void getStatus(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
     method public boolean hasSession();
+    method public boolean isMessagingSupported();
     method public boolean isQueuingSupported();
     method public boolean isRemotePlaybackSupported();
     method public boolean isSessionManagementSupported();
@@ -664,6 +701,8 @@
     method public void remove(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
     method public void resume(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
     method public void seek(java.lang.String, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+    method public void sendMessage(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+    method public void setMessageCallback(android.support.v7.media.RemotePlaybackClient.MessageCallback);
     method public void setSessionId(java.lang.String);
     method public void setStatusCallback(android.support.v7.media.RemotePlaybackClient.StatusCallback);
     method public void startSession(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
@@ -680,6 +719,11 @@
     method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus, java.lang.String, android.support.v7.media.MediaItemStatus);
   }
 
+  public static abstract class RemotePlaybackClient.MessageCallback {
+    ctor public RemotePlaybackClient.MessageCallback();
+    method public void onMessageReceived(java.lang.String, android.os.Bundle);
+  }
+
   public static abstract class RemotePlaybackClient.SessionActionCallback extends android.support.v7.media.RemotePlaybackClient.ActionCallback {
     ctor public RemotePlaybackClient.SessionActionCallback();
     method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus);
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
similarity index 63%
copy from v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
copy to v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
index 8126b38..3734b59 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.support.v7.widget.test;
+package android.support.v7.media;
 
-import android.app.Activity;
-
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
+final class MediaRouterApi24 {
+    public static final class RouteInfo {
+        public static int getDeviceType(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getDeviceType();
+        }
+    }
 }
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 37224f0..883f939 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -16,6 +16,7 @@
     jellybean.java.srcDir 'jellybean'
     jellybeanmr1.java.srcDir 'jellybean-mr1'
     jellybeanmr2.java.srcDir 'jellybean-mr2'
+    api24.java.srcDir 'api24'
 }
 
 // create a jar task for the code above
@@ -23,6 +24,7 @@
     from sourceSets.jellybean.output
     from sourceSets.jellybeanmr1.output
     from sourceSets.jellybeanmr2.output
+    from sourceSets.api24.output
     baseName "internal_impl"
 }
 
@@ -33,10 +35,15 @@
     jellybeanmr1Compile getAndroidPrebuilt('17')
     jellybeanmr1Compile sourceSets.jellybean.output
 
-    jellybeanmr2Compile getAndroidPrebuilt('current')
+    jellybeanmr2Compile getAndroidPrebuilt('18')
     jellybeanmr2Compile sourceSets.jellybean.output
     jellybeanmr2Compile sourceSets.jellybeanmr1.output
 
+    api24Compile getAndroidPrebuilt('current')
+    api24Compile sourceSets.jellybean.output
+    api24Compile sourceSets.jellybeanmr1.output
+    api24Compile sourceSets.jellybeanmr2.output
+
     compile files(jar.archivePath)
 }
 
@@ -105,6 +112,8 @@
     sourcesJarTask.from project.sourceSets.jellybeanmr1.java.srcDirs
     javadocTask.source  project.sourceSets.jellybeanmr2.java
     sourcesJarTask.from project.sourceSets.jellybeanmr2.java.srcDirs
+    javadocTask.source  project.sourceSets.api24.java
+    sourcesJarTask.from project.sourceSets.api24.java.srcDirs
 
     artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
diff --git a/v7/mediarouter/res/values-b+sr+Latn/strings.xml b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..c10c42d
--- /dev/null
+++ b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme Prebaci"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Prebacujte na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+    <string name="mr_controller_stop" msgid="4570331844078181931">"Zaustavi prebacivanje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Pusti"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nema izabranih medija"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nisu dostupne nikakve informacije"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-be-rBY/strings.xml b/v7/mediarouter/res/values-be-rBY/strings.xml
new file mode 100644
index 0000000..311e001
--- /dev/null
+++ b/v7/mediarouter/res/values-be-rBY/strings.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Сістэма"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Прылады"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Кнопка трансляцыі"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Трансляваць на"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Пошук прылад"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Адлучыць"</string>
+    <string name="mr_controller_stop" msgid="4570331844078181931">"Спыніць трансляцыю"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Закрыць"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Прайграць"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Прыпыніць"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгарнуць"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згарнуць"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медыяфайл не выбраны"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Інфармацыя адсутнічае"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экран трансляцыі"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-bs-rBA/strings.xml b/v7/mediarouter/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..1e262a3
--- /dev/null
+++ b/v7/mediarouter/res/values-bs-rBA/strings.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme za prebacivanje"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Prebacujte na"</string>
+    <string name="mr_chooser_searching" msgid="6349900579507521956">"Traženje uređaja"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+    <string name="mr_controller_stop" msgid="4570331844078181931">"Zaustavi prebacivanje"</string>
+    <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+    <string name="mr_controller_play" msgid="683634565969987458">"Reproduciraj"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+    <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+    <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+    <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nijedan medij nije odabran"</string>
+    <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nema dostupnih informacija"</string>
+    <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
index 87a81ba..2c812ae 100644
--- a/v7/mediarouter/res/values-fa/strings.xml
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -25,7 +25,7 @@
     <string name="mr_controller_stop" msgid="4570331844078181931">"توقف ارسال محتوا"</string>
     <string name="mr_controller_close_description" msgid="7333862312480583260">"بستن"</string>
     <string name="mr_controller_play" msgid="683634565969987458">"پخش"</string>
-    <string name="mr_controller_pause" msgid="5451884435510905406">"توقف موقت"</string>
+    <string name="mr_controller_pause" msgid="5451884435510905406">"مکث"</string>
     <string name="mr_controller_expand_group" msgid="8062427022744266907">"بزرگ کردن"</string>
     <string name="mr_controller_collapse_group" msgid="7924809056904240926">"کوچک کردن"</string>
     <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"رسانه انتخاب نشده است"</string>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
index de94f18..6e14ccc 100644
--- a/v7/mediarouter/res/values-fr/strings.xml
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -19,7 +19,7 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"Icône Cast"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"Diffuser sur"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Caster sur"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Recherche d\'appareils en cours…"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Déconnecter"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"Arrêter de diffuser"</string>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
index 85bd488..b05c17c 100644
--- a/v7/mediarouter/res/values-hy-rAM/strings.xml
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -19,7 +19,7 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"Համակարգ"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Սարքեր"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"Հեռարձակման կոճակ"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"Հեռարձակել դեպի"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Ընտրեք սարքը"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Սարքերի որոնում"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Անջատել"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"Դադարեցնել հեռարձակումը"</string>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 3d01880..4d84852 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -19,7 +19,7 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"Tombol transmisi"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"Transmisi ke"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"Transmisikan ke"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"Mencari perangkat"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"Putuskan sambungan"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"Hentikan transmisi"</string>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
index ea8d838..ffe9054 100644
--- a/v7/mediarouter/res/values-ja/strings.xml
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -19,7 +19,7 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"システム"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"端末"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"キャストアイコン"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"キャスト先"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"キャストするデバイス"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"端末を検索しています"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"接続を解除"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"キャストを停止"</string>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
index 2b3455f..fc1dbac 100644
--- a/v7/mediarouter/res/values-ko/strings.xml
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -19,7 +19,7 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"시스템"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"기기"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"전송 버튼"</string>
-    <string name="mr_chooser_title" msgid="414301941546135990">"전송 대상"</string>
+    <string name="mr_chooser_title" msgid="414301941546135990">"전송할 기기"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"기기를 찾는 중"</string>
     <string name="mr_controller_disconnect" msgid="1227264889412989580">"연결 해제"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"전송 중지"</string>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index bc961a7..e92b5fa 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -218,7 +218,8 @@
                     // In API 4 ~ 10, AsyncTasks are running in parallel. Needs synchronization.
                     synchronized (MediaRouteChooserDialog.this) {
                         if (!isCancelled()) {
-                            RouteComparator.loadRouteUsageScores(getContext(), mNewRoutes);
+                            RouteComparator.getInstance(getContext())
+                                    .loadRouteUsageScores(mNewRoutes);
                         }
                     }
                     return null;
@@ -317,7 +318,8 @@
 
                     @Override
                     protected Void doInBackground(Void... params) {
-                        RouteComparator.storeRouteUsageScores(getContext(), route.getId());
+                        RouteComparator.getInstance(getContext())
+                                .storeRouteUsageScores(route.getId());
                         return null;
                     }
 
@@ -401,8 +403,21 @@
         private static final float MIN_USAGE_SCORE = 0.1f;
         private static final float USAGE_SCORE_DECAY_FACTOR = 0.95f;
 
-        public static final RouteComparator sInstance = new RouteComparator();
-        public static final HashMap<String, Float> sRouteUsageScoreMap = new HashMap();
+        private static RouteComparator sInstance;
+        private final HashMap<String, Float> mRouteUsageScoreMap;
+        private final SharedPreferences mPreferences;
+
+        public static RouteComparator getInstance(Context context) {
+            if (sInstance == null) {
+                sInstance = new RouteComparator(context);
+            }
+            return sInstance;
+        }
+
+        private RouteComparator(Context context) {
+            mRouteUsageScoreMap = new HashMap();
+            mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+        }
 
         @Override
         public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
@@ -418,11 +433,11 @@
             } else if (rhs.isDeviceTypeBluetooth()) {
                 return -1;
             }
-            Float lhsUsageScore = sRouteUsageScoreMap.get(lhs.getId());
+            Float lhsUsageScore = mRouteUsageScoreMap.get(lhs.getId());
             if (lhsUsageScore == null) {
                 lhsUsageScore = 0f;
             }
-            Float rhsUsageScore = sRouteUsageScoreMap.get(rhs.getId());
+            Float rhsUsageScore = mRouteUsageScoreMap.get(rhs.getId());
             if (rhsUsageScore == null) {
                 rhsUsageScore = 0f;
             }
@@ -432,21 +447,19 @@
             return lhs.getName().compareTo(rhs.getName());
         }
 
-        private static void loadRouteUsageScores(
-                Context context, List<MediaRouter.RouteInfo> routes) {
-            sRouteUsageScoreMap.clear();
-            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+        private void loadRouteUsageScores(List<MediaRouter.RouteInfo> routes) {
             for (MediaRouter.RouteInfo route : routes) {
-                sRouteUsageScoreMap.put(route.getId(),
-                        preferences.getFloat(PREF_USAGE_SCORE_PREFIX + route.getId(), 0f));
+                if (mRouteUsageScoreMap.get(route.getId()) == null) {
+                    mRouteUsageScoreMap.put(route.getId(),
+                            mPreferences.getFloat(PREF_USAGE_SCORE_PREFIX + route.getId(), 0f));
+                }
             }
         }
 
-        private static void storeRouteUsageScores(Context context, String selectedRouteId) {
-            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
-            SharedPreferences.Editor prefEditor = preferences.edit();
-            List<String> routeIds = new ArrayList<String>(
-                    Arrays.asList(preferences.getString(PREF_ROUTE_IDS, "").split(",")));
+        private void storeRouteUsageScores(String selectedRouteId) {
+            SharedPreferences.Editor prefEditor = mPreferences.edit();
+            List<String> routeIds = new ArrayList<>(
+                    Arrays.asList(mPreferences.getString(PREF_ROUTE_IDS, "").split(",")));
             if (!routeIds.contains(selectedRouteId)) {
                 routeIds.add(selectedRouteId);
             }
@@ -457,14 +470,16 @@
                 // 2) 0, if usageScore * USAGE_SCORE_DECAY_FACTOR < MIN_USAGE_SCORE, or
                 // 3) usageScore * USAGE_SCORE_DECAY_FACTOR, otherwise,
                 String routeUsageScoreKey = PREF_USAGE_SCORE_PREFIX + routeId;
-                float newUsageScore = preferences.getFloat(routeUsageScoreKey, 0f)
+                float newUsageScore = mPreferences.getFloat(routeUsageScoreKey, 0f)
                         * USAGE_SCORE_DECAY_FACTOR;
                 if (selectedRouteId.equals(routeId)) {
                     newUsageScore += 1f;
                 }
                 if (newUsageScore < MIN_USAGE_SCORE) {
+                    mRouteUsageScoreMap.remove(routeId);
                     prefEditor.remove(routeId);
                 } else {
+                    mRouteUsageScoreMap.put(routeId, newUsageScore);
                     prefEditor.putFloat(routeUsageScoreKey, newUsageScore);
                     if (routeIdsBuilder.length() > 0) {
                         routeIdsBuilder.append(',');
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
index 638701f..55734c9 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
@@ -765,6 +765,9 @@
      * <li>{@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
      * {@link PendingIntent} for a broadcast receiver that will receive status updates
      * about the media session.
+     * <li>{@link #EXTRA_MESSAGE_RECEIVER} <em>(optional)</em>: Specifies a
+     * {@link PendingIntent} for a broadcast receiver that will receive messages from
+     * the media session.
      * </ul>
      *
      * <h3>Result data</h3>
@@ -787,6 +790,15 @@
      * Refer to {@link MediaSessionStatus} for details.
      * </p>
      *
+     * <h3>Custom messages</h3>
+     * <p>
+     * If the client supplies a {@link #EXTRA_MESSAGE_RECEIVER message receiver}
+     * then the media route provider is responsible for sending messages to the receiver
+     * when the session has any messages to send.
+     * </p><p>
+     * Refer to {@link #EXTRA_MESSAGE} for details.
+     * </p>
+     *
      * <h3>Errors</h3>
      * <p>
      * This action returns an error if the session could not be created.
@@ -880,6 +892,29 @@
      */
     public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
 
+    /**
+     * Custom media control action: Send {link #EXTRA_MESSAGE}.
+     * <p>
+     * This action asks a route to handle a message described by EXTRA_MESSAGE.
+     * </p>
+     *
+     * <h3>Request parameters</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+     * to which will handle this message.
+     * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message to send.
+     * </ul>
+     *
+     * <h3>Result data</h3>
+     * Any messages defined by each media route provider.
+     *
+     * <h3>Errors</h3>
+     * Any error messages defined by each media route provider.
+     *
+     * @see MediaRouter.RouteInfo#sendControlRequest
+     */
+    public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+
     /* Extras and related constants. */
 
     /**
@@ -944,7 +979,7 @@
             "android.media.intent.extra.SESSION_STATUS";
 
     /**
-     * Bundle extra: Media item status update receiver.
+     * Bundle extra: Media session status update receiver.
      * <p>
      * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
      * broadcast receiver that will receive status updates about the media session.
@@ -970,6 +1005,32 @@
             "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
 
     /**
+     * Bundle extra: Media message receiver.
+     * <p>
+     * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+     * broadcast receiver that will receive messages from the media session.
+     * </p><p>
+     * When the media session has a message to send, the media route provider will
+     * send a broadcast to the pending intent with extras that identify the session
+     * id and its message.
+     * </p><p>
+     * The value is a {@link PendingIntent}.
+     * </p>
+     *
+     * <h3>Broadcast extras</h3>
+     * <ul>
+     * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+     * the session.
+     * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message from
+     * the session as a bundle object.
+     * </ul>
+     *
+     * @see #ACTION_START_SESSION
+     */
+    public static final String EXTRA_MESSAGE_RECEIVER =
+            "android.media.intent.extra.MESSAGE_RECEIVER";
+
+    /**
      * Bundle extra: Media item id.
      * <p>
      * An opaque unique identifier returned as a result from {@link #ACTION_PLAY} or
@@ -1111,6 +1172,16 @@
             "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
 
     /**
+     * Bundle extra: Message.
+     * <p>
+     * Used with {@link #ACTION_SEND_MESSAGE}, and included in broadcast intents sent to
+     * {@link #EXTRA_MESSAGE_RECEIVER message receivers} to describe a message between a
+     * session and a media route provider.
+     * </p><p>
+     */
+    public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+
+    /**
      * Integer extra: Error code.
      * <p>
      * Used with all media control requests to describe the cause of an error.
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index b2cb289..113f55d 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -24,6 +24,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import java.util.Iterator;
+
 /**
  * A helper class for playing media on remote routes using the remote playback protocol
  * defined by {@link MediaControlIntent}.
@@ -38,16 +40,19 @@
 
     private final Context mContext;
     private final MediaRouter.RouteInfo mRoute;
-    private final StatusReceiver mStatusReceiver;
+    private final ActionReceiver mActionReceiver;
     private final PendingIntent mItemStatusPendingIntent;
     private final PendingIntent mSessionStatusPendingIntent;
+    private final PendingIntent mMessagePendingIntent;
 
     private boolean mRouteSupportsRemotePlayback;
     private boolean mRouteSupportsQueuing;
     private boolean mRouteSupportsSessionManagement;
+    private boolean mRouteSupportsMessaging;
 
     private String mSessionId;
     private StatusCallback mStatusCallback;
+    private MessageCallback mMessageCallback;
 
     /**
      * Creates a remote playback client for a route.
@@ -65,22 +70,27 @@
         mContext = context;
         mRoute = route;
 
-        IntentFilter statusFilter = new IntentFilter();
-        statusFilter.addAction(StatusReceiver.ACTION_ITEM_STATUS_CHANGED);
-        statusFilter.addAction(StatusReceiver.ACTION_SESSION_STATUS_CHANGED);
-        mStatusReceiver = new StatusReceiver();
-        context.registerReceiver(mStatusReceiver, statusFilter);
+        IntentFilter actionFilter = new IntentFilter();
+        actionFilter.addAction(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+        actionFilter.addAction(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+        actionFilter.addAction(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+        mActionReceiver = new ActionReceiver();
+        context.registerReceiver(mActionReceiver, actionFilter);
 
-        Intent itemStatusIntent = new Intent(StatusReceiver.ACTION_ITEM_STATUS_CHANGED);
+        Intent itemStatusIntent = new Intent(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
         itemStatusIntent.setPackage(context.getPackageName());
         mItemStatusPendingIntent = PendingIntent.getBroadcast(
                 context, 0, itemStatusIntent, 0);
 
-        Intent sessionStatusIntent = new Intent(StatusReceiver.ACTION_SESSION_STATUS_CHANGED);
+        Intent sessionStatusIntent = new Intent(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
         sessionStatusIntent.setPackage(context.getPackageName());
         mSessionStatusPendingIntent = PendingIntent.getBroadcast(
                 context, 0, sessionStatusIntent, 0);
 
+        Intent messageIntent = new Intent(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+        messageIntent.setPackage(context.getPackageName());
+        mMessagePendingIntent = PendingIntent.getBroadcast(
+                context, 0, messageIntent, 0);
         detectFeatures();
     }
 
@@ -88,7 +98,7 @@
      * Releases resources owned by the client.
      */
     public void release() {
-        mContext.unregisterReceiver(mStatusReceiver);
+        mContext.unregisterReceiver(mActionReceiver);
     }
 
     /**
@@ -156,6 +166,25 @@
     }
 
     /**
+     * Returns true if the route supports messages.
+     * <p>
+     * This method returns true if the route supports all of the basic remote playback
+     * actions and all of the following actions:
+     * {@link MediaControlIntent#ACTION_START_SESSION start session},
+     * {@link MediaControlIntent#ACTION_SEND_MESSAGE send message},
+     * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+     * </p>
+     *
+     * @return True if session management is supported.
+     * Implies {@link #isRemotePlaybackSupported} is also true.
+     *
+     * @see #isRemotePlaybackSupported
+     */
+    public boolean isMessagingSupported() {
+        return mRouteSupportsMessaging;
+    }
+
+    /**
      * Gets the current session id if there is one.
      *
      * @return The current session id, or null if none.
@@ -215,6 +244,19 @@
     }
 
     /**
+     * Sets a callback that should receive messages when a message is sent from
+     * media sessions created by this instance of the remote playback client changes.
+     * <p>
+     * The callback should be set before the session is created.
+     * </p>
+     *
+     * @param callback The callback to set.  May be null to remove the previous callback.
+     */
+    public void setMessageCallback(MessageCallback callback) {
+        mMessageCallback = callback;
+    }
+
+    /**
      * Sends a request to play a media item.
      * <p>
      * Clears the queue and starts playing the new item immediately.  If the queue
@@ -516,10 +558,39 @@
         Intent intent = new Intent(MediaControlIntent.ACTION_START_SESSION);
         intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER,
                 mSessionStatusPendingIntent);
+        if (mRouteSupportsMessaging) {
+            intent.putExtra(MediaControlIntent.EXTRA_MESSAGE_RECEIVER, mMessagePendingIntent);
+        }
         performSessionAction(intent, null, extras, callback);
     }
 
     /**
+     * Sends a message.
+     * <p>
+     * The request is issued in the current session.
+     * </p><p>
+     * Please refer to {@link MediaControlIntent#ACTION_SEND_MESSAGE} for
+     * more information about the semantics of this request.
+     * </p>
+     *
+     * @param message A bundle message denoting {link MediaControlIntent#EXTRA_SESSION_MESSAGE}.
+     * @param callback A callback to invoke when the request has been processed, or null if none.
+     *
+     * @throws IllegalStateException if there is no current session.
+     * @throws UnsupportedOperationException if the route does not support messages.
+     *
+     * @see MediaControlIntent#ACTION_SEND_MESSAGE
+     * @see #isMessagingSupported
+     */
+    public void sendMessage(Bundle message, SessionActionCallback callback) {
+        throwIfNoCurrentSession();
+        throwIfMessageNotSupported();
+
+        Intent intent = new Intent(MediaControlIntent.ACTION_SEND_MESSAGE);
+        performSessionAction(intent, mSessionId, message, callback);
+    }
+
+    /**
      * Sends a request to get the status of the media playback session.
      * <p>
      * The request is issued in the current session.
@@ -722,12 +793,22 @@
                 && routeSupportsAction(MediaControlIntent.ACTION_START_SESSION)
                 && routeSupportsAction(MediaControlIntent.ACTION_GET_SESSION_STATUS)
                 && routeSupportsAction(MediaControlIntent.ACTION_END_SESSION);
+        mRouteSupportsMessaging = doesRouteSupportMessaging();
     }
 
     private boolean routeSupportsAction(String action) {
         return mRoute.supportsControlAction(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK, action);
     }
 
+    private boolean doesRouteSupportMessaging() {
+        for (IntentFilter filter : mRoute.getControlFilters()) {
+            if (filter.hasAction(MediaControlIntent.ACTION_SEND_MESSAGE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void throwIfRemotePlaybackNotSupported() {
         if (!mRouteSupportsRemotePlayback) {
             throw new UnsupportedOperationException("The route does not support remote playback.");
@@ -747,6 +828,12 @@
         }
     }
 
+    private void throwIfMessageNotSupported() {
+        if (!mRouteSupportsMessaging) {
+            throw new UnsupportedOperationException("The route does not support message.");
+        }
+    }
+
     private void throwIfNoCurrentSession() {
         if (mSessionId == null) {
             throw new IllegalStateException("There is no current session.");
@@ -780,11 +867,13 @@
         return "null";
     }
 
-    private final class StatusReceiver extends BroadcastReceiver {
+    private final class ActionReceiver extends BroadcastReceiver {
         public static final String ACTION_ITEM_STATUS_CHANGED =
                 "android.support.v7.media.actions.ACTION_ITEM_STATUS_CHANGED";
         public static final String ACTION_SESSION_STATUS_CHANGED =
                 "android.support.v7.media.actions.ACTION_SESSION_STATUS_CHANGED";
+        public static final String ACTION_MESSAGE_RECEIVED =
+                "android.support.v7.media.actions.ACTION_MESSAGE_RECEIVED";
 
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -839,6 +928,15 @@
                     mStatusCallback.onSessionStatusChanged(intent.getExtras(),
                             sessionId, sessionStatus);
                 }
+            } else if (action.equals(ACTION_MESSAGE_RECEIVED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Received message callback: sessionId=" + sessionId);
+                }
+
+                if (mMessageCallback != null) {
+                    mMessageCallback.onMessageReceived(sessionId,
+                            intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
+                }
             }
         }
     }
@@ -881,6 +979,17 @@
         }
     }
 
+    public static abstract class MessageCallback {
+        /**
+         * Called when a message received.
+         *
+         * @param sessionId The session id.
+         * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+         */
+        public void onMessageReceived(String sessionId, Bundle message) {
+        }
+    }
+
     /**
      * Base callback type for remote playback requests.
      */
diff --git a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
index 6a09650..21ff5b4 100644
--- a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
@@ -49,6 +49,9 @@
     }
 
     public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
+        if (Build.VERSION.SDK_INT >= 24) {
+            return new Api24Impl(context, syncCallback);
+        }
         if (Build.VERSION.SDK_INT >= 18) {
             return new JellybeanMr2Impl(context, syncCallback);
         }
@@ -836,4 +839,21 @@
             return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
         }
     }
+
+    /**
+     * Api24 implementation.
+     */
+    private static class Api24Impl extends JellybeanMr2Impl {
+        public Api24Impl(Context context, SyncCallback syncCallback) {
+            super(context, syncCallback);
+        }
+
+        @Override
+        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+                                                    MediaRouteDescriptor.Builder builder) {
+            super.onBuildSystemRouteDescriptor(record, builder);
+
+            builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
+        }
+    }
 }
diff --git a/v7/palette/Android.mk b/v7/palette/Android.mk
index 5c9898f..0c4cb05 100644
--- a/v7/palette/Android.mk
+++ b/v7/palette/Android.mk
@@ -21,6 +21,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main)
 LOCAL_MANIFEST_FILE := $(LOCAL_PATH)/src/main/AndroidManifest.xml
 LOCAL_JAVA_LIBRARIES += android-support-v4
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/preference/Android.mk b/v7/preference/Android.mk
index d12b479..85ded02 100644
--- a/v7/preference/Android.mk
+++ b/v7/preference/Android.mk
@@ -20,8 +20,8 @@
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v7-preference-res
+LOCAL_SRC_FILES := $(call all-java-files-under, constants)
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
 LOCAL_RESOURCE_DIR := \
         frameworks/support/v7/appcompat/res \
         frameworks/support/v7/recyclerview/res \
@@ -29,6 +29,7 @@
 LOCAL_AAPT_FLAGS := \
 	--auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Here is the final static library that apps can link against.
@@ -46,6 +47,7 @@
         android-support-v7-recyclerview \
         android-support-annotations \
         android-support-v7-preference-res
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index 26a3e12..dc8f91c 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -35,6 +35,14 @@
     method public abstract android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
   }
 
+  public class DropDownPreference extends android.support.v7.preference.ListPreference {
+    ctor public DropDownPreference(android.content.Context);
+    ctor public DropDownPreference(android.content.Context, android.util.AttributeSet);
+    ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int);
+    ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int, int);
+    method protected android.widget.ArrayAdapter createAdapter();
+  }
+
   public class EditTextPreference extends android.support.v7.preference.DialogPreference {
     ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int, int);
     ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int);
@@ -74,7 +82,7 @@
     method public void onDialogClosed(boolean);
   }
 
-  public class Preference {
+  public class Preference implements java.lang.Comparable {
     ctor public Preference(android.content.Context, android.util.AttributeSet, int, int);
     ctor public Preference(android.content.Context, android.util.AttributeSet, int);
     ctor public Preference(android.content.Context, android.util.AttributeSet);
@@ -117,6 +125,7 @@
     method public void onBindViewHolder(android.support.v7.preference.PreferenceViewHolder);
     method protected void onClick();
     method public void onDependencyChanged(android.support.v7.preference.Preference, boolean);
+    method public void onDetached();
     method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int);
     method public void onParentChanged(android.support.v7.preference.Preference, boolean);
     method protected void onPrepareForRemoval();
@@ -261,6 +270,8 @@
     method public boolean setPreferences(android.support.v7.preference.PreferenceScreen);
     method public void setSharedPreferencesMode(int);
     method public void setSharedPreferencesName(java.lang.String);
+    method public void setStorageDefault();
+    method public void setStorageDeviceProtected();
     method public void showDialog(android.support.v7.preference.Preference);
     field public static final java.lang.String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
   }
diff --git a/v7/preference/api/removed.txt b/v7/preference/api/removed.txt
index e69de29..b715257 100644
--- a/v7/preference/api/removed.txt
+++ b/v7/preference/api/removed.txt
@@ -0,0 +1,8 @@
+package android.support.v7.preference {
+
+  public class PreferenceManager {
+    method public deprecated void setStorageDeviceEncrypted();
+  }
+
+}
+
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 45b4f96..1693b3a 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -30,6 +30,7 @@
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
         main.java.srcDir 'src'
+        main.java.srcDir 'constants'
         main.res.srcDir 'res'
         main.assets.srcDir 'assets'
         main.resources.srcDir 'src'
diff --git a/v7/preference/constants/android/support/v7/preference/AndroidResources.java b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
new file mode 100644
index 0000000..d529ad2
--- /dev/null
+++ b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
@@ -0,0 +1,29 @@
+/*
+ * 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.preference;
+
+public class AndroidResources {
+
+    public static final int ANDROID_R_ICON_FRAME = android.R.id.icon_frame;
+    public static final int ANDROID_R_LIST_CONTAINER = android.R.id.list_container;
+    public static final int ANDROID_R_SWITCH_WIDGET = android.R.id.switch_widget;
+    public static final int ANDROID_R_PREFERENCE_FRAGMENT_STYLE
+            = android.R.attr.preferenceFragmentStyle;
+    public static final int ANDROID_R_EDITTEXT_PREFERENCE_STYLE
+            = android.R.attr.editTextPreferenceStyle;
+
+}
diff --git a/v7/preference/res/layout/preference_dropdown.xml b/v7/preference/res/layout/preference_dropdown.xml
new file mode 100644
index 0000000..09ea8fb
--- /dev/null
+++ b/v7/preference/res/layout/preference_dropdown.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:visibility="invisible" />
+
+    <include layout="@layout/preference" />
+
+</FrameLayout>
diff --git a/v7/preference/res/layout/preference_list_fragment.xml b/v7/preference/res/layout/preference_list_fragment.xml
index 44c5438..fe11fb2 100644
--- a/v7/preference/res/layout/preference_list_fragment.xml
+++ b/v7/preference/res/layout/preference_list_fragment.xml
@@ -21,7 +21,7 @@
     android:layout_width="match_parent" >
 
     <FrameLayout
-        android:id="@+id/list_container"
+        android:id="@android:id/list_container"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1" />
diff --git a/v7/preference/res/layout/preference_widget_checkbox.xml b/v7/preference/res/layout/preference_widget_checkbox.xml
index e53b7df..cc99443 100644
--- a/v7/preference/res/layout/preference_widget_checkbox.xml
+++ b/v7/preference/res/layout/preference_widget_checkbox.xml
@@ -18,7 +18,7 @@
 <!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
      inside android.R.layout.preference. -->
 <CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/checkbox"
+    android:id="@android:id/checkbox"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:focusable="false"
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index cd6f325..bd64109 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -50,6 +50,8 @@
         <attr name="editTextPreferenceStyle" format="reference" />
         <!-- Default style for RingtonePreference. -->
         <attr name="ringtonePreferenceStyle" format="reference" />
+        <!-- Default style for DropDownPreference. -->
+        <attr name="dropdownPreferenceStyle" format="reference" />
         <!-- The preference layout that has the child/tabbed effect. -->
         <attr name="preferenceLayoutChild" format="reference" />
         <!-- Preference panel style -->
@@ -245,4 +247,11 @@
         <attr name="android:maxHeight" />
     </declare-styleable>
 
+    <!-- Used to access some android attrs -->
+    <declare-styleable name="BackgroundStyle">
+        <attr name="android:selectableItemBackground" />
+        <!-- Need a non-android: attr here so that gradle doesn't remove it -->
+        <attr name="selectableItemBackground" />
+    </declare-styleable>
+
 </resources>
diff --git a/v7/preference/res/values/styles.xml b/v7/preference/res/values/styles.xml
index 774c0c9..06b24fa 100644
--- a/v7/preference/res/values/styles.xml
+++ b/v7/preference/res/values/styles.xml
@@ -65,4 +65,8 @@
         <item name="android:paddingLeft">16dp</item>
         <item name="android:paddingRight">16dp</item>
     </style>
+
+    <style name="Preference.DropDown">
+        <item name="android:layout">@layout/preference_dropdown</item>
+    </style>
 </resources>
diff --git a/v7/preference/res/values/themes.xml b/v7/preference/res/values/themes.xml
index 8a7eaa3..bb7f496 100644
--- a/v7/preference/res/values/themes.xml
+++ b/v7/preference/res/values/themes.xml
@@ -27,5 +27,6 @@
         <item name="dialogPreferenceStyle">@style/Preference.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList</item>
+        <item name="dropdownPreferenceStyle">@style/Preference.DropDown</item>
     </style>
 </resources>
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index 61eb13c..50ab1db 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Checkable;
+import android.widget.CompoundButton;
 
 /**
  * A {@link Preference} that provides checkbox widget
@@ -35,6 +36,20 @@
  * @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
  */
 public class CheckBoxPreference extends TwoStatePreference {
+    private final Listener mListener = new Listener();
+
+    private class Listener implements CompoundButton.OnCheckedChangeListener {
+        @Override
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            if (!callChangeListener(isChecked)) {
+                // Listener didn't like it, change it back.
+                // CompoundButton will make sure we don't recurse.
+                buttonView.setChecked(!isChecked);
+                return;
+            }
+            CheckBoxPreference.this.setChecked(isChecked);
+        }
+    }
 
     public CheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
@@ -61,7 +76,8 @@
     }
 
     public CheckBoxPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.checkBoxPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.checkBoxPreferenceStyle,
+                android.R.attr.checkBoxPreferenceStyle));
     }
 
     public CheckBoxPreference(Context context) {
@@ -72,10 +88,7 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
 
-        View checkboxView = holder.findViewById(R.id.checkbox);
-        if (checkboxView != null && checkboxView instanceof Checkable) {
-            ((Checkable) checkboxView).setChecked(mChecked);
-        }
+        syncCheckboxView(holder.findViewById(android.R.id.checkbox));
 
         syncSummaryView(holder);
     }
@@ -104,8 +117,14 @@
     }
 
     private void syncCheckboxView(View view) {
+        if (view instanceof CompoundButton) {
+            ((CompoundButton) view).setOnCheckedChangeListener(null);
+        }
         if (view instanceof Checkable) {
             ((Checkable) view).setChecked(mChecked);
         }
+        if (view instanceof CompoundButton) {
+            ((CompoundButton) view).setOnCheckedChangeListener(mListener);
+        }
     }
 }
diff --git a/v7/preference/src/android/support/v7/preference/DialogPreference.java b/v7/preference/src/android/support/v7/preference/DialogPreference.java
index bf0f4ef..158accb 100644
--- a/v7/preference/src/android/support/v7/preference/DialogPreference.java
+++ b/v7/preference/src/android/support/v7/preference/DialogPreference.java
@@ -90,7 +90,8 @@
     }
 
     public DialogPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.dialogPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+                android.R.attr.dialogPreferenceStyle));
     }
 
     public DialogPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/DropDownPreference.java b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
new file mode 100644
index 0000000..e7a0514
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
@@ -0,0 +1,141 @@
+/*
+ * 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.preference;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+
+public class DropDownPreference extends ListPreference {
+
+    private final Context mContext;
+    private final ArrayAdapter<String> mAdapter;
+
+    private Spinner mSpinner;
+
+    public DropDownPreference(Context context) {
+        this(context, null);
+    }
+
+    public DropDownPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.dropdownPreferenceStyle);
+    }
+
+    public DropDownPreference(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, 0);
+    }
+
+    public DropDownPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mContext = context;
+        mAdapter = createAdapter();
+
+        updateEntries();
+    }
+
+    @Override
+    protected void onClick() {
+        mSpinner.performClick();
+    }
+
+    @Override
+    public void setEntries(@NonNull CharSequence[] entries) {
+        super.setEntries(entries);
+        updateEntries();
+    }
+
+
+    /**
+     * By default, this class uses a simple {@link android.widget.ArrayAdapter}. But if you need
+     * a more complicated {@link android.widget.ArrayAdapter}, this method can be overriden to
+     * create a custom one.
+     * <p> Note: This method is called from the constructor. So, overriden methods will get called
+     * before any subclass initialization.
+     *
+     * @return The custom {@link android.widget.ArrayAdapter} that needs to be used with this class.
+     */
+    protected ArrayAdapter createAdapter() {
+        return new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_dropdown_item);
+    }
+
+    private void updateEntries() {
+        mAdapter.clear();
+        if (getEntries() != null) {
+            for (CharSequence c : getEntries()) {
+                mAdapter.add(c.toString());
+            }
+        }
+    }
+
+    public void setValueIndex(int index) {
+        setValue(getEntryValues()[index].toString());
+    }
+
+    /**
+     * @hide
+     */
+    public int findSpinnerIndexOfValue(String value) {
+        CharSequence[] entryValues = getEntryValues();
+        if (value != null && entryValues != null) {
+            for (int i = entryValues.length - 1; i >= 0; i--) {
+                if (entryValues[i].equals(value)) {
+                    return i;
+                }
+            }
+        }
+        return Spinner.INVALID_POSITION;
+    }
+
+    @Override
+    protected void notifyChanged() {
+        super.notifyChanged();
+        mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        mSpinner = (Spinner) view.itemView.findViewById(R.id.spinner);
+        mSpinner.setAdapter(mAdapter);
+        mSpinner.setOnItemSelectedListener(mItemSelectedListener);
+        mSpinner.setSelection(findSpinnerIndexOfValue(getValue()));
+        super.onBindViewHolder(view);
+    }
+
+    private final OnItemSelectedListener mItemSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
+            if (position >= 0) {
+                String value = getEntryValues()[position].toString();
+                if (!value.equals(getValue()) && callChangeListener(value)) {
+                    setValue(value);
+                }
+            }
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+            // noop
+        }
+    };
+}
+
diff --git a/v7/preference/src/android/support/v7/preference/EditTextPreference.java b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
index c4941e6..423a0c9 100644
--- a/v7/preference/src/android/support/v7/preference/EditTextPreference.java
+++ b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.content.res.TypedArrayUtils;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.widget.EditText;
@@ -46,7 +47,8 @@
     }
 
     public EditTextPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.editTextPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.editTextPreferenceStyle,
+                AndroidResources.ANDROID_R_EDITTEXT_PREFERENCE_STYLE));
     }
 
     public EditTextPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/ListPreference.java b/v7/preference/src/android/support/v7/preference/ListPreference.java
index 848cfad..4507b26 100644
--- a/v7/preference/src/android/support/v7/preference/ListPreference.java
+++ b/v7/preference/src/android/support/v7/preference/ListPreference.java
@@ -74,7 +74,8 @@
     }
 
     public ListPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.dialogPreferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+                android.R.attr.dialogPreferenceStyle));
     }
 
     public ListPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index ebff2d7..175f855 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -278,7 +278,7 @@
 
         mShouldDisableView =
                 TypedArrayUtils.getBoolean(a, R.styleable.Preference_shouldDisableView,
-                        R.styleable.Preference_shouldDisableView, true);
+                        R.styleable.Preference_android_shouldDisableView, true);
 
         a.recycle();
     }
@@ -322,7 +322,8 @@
      * @see #Preference(Context, AttributeSet, int)
      */
     public Preference(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.preferenceStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+                android.R.attr.preferenceStyle));
     }
 
     /**
@@ -512,7 +513,10 @@
             imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
         }
 
-        final View imageFrame = holder.findViewById(R.id.icon_frame);
+        View imageFrame = holder.findViewById(R.id.icon_frame);
+        if (imageFrame == null) {
+            imageFrame = holder.findViewById(AndroidResources.ANDROID_R_ICON_FRAME);
+        }
         if (imageFrame != null) {
             imageFrame.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
         }
@@ -525,6 +529,7 @@
 
         final boolean selectable = isSelectable();
         holder.itemView.setFocusable(selectable);
+        holder.itemView.setClickable(selectable);
 
         holder.setDividerAllowedAbove(selectable);
         holder.setDividerAllowedBelow(selectable);
@@ -1105,6 +1110,16 @@
         registerDependency();
     }
 
+    /**
+     * Called when the Preference hierarchy has been detached from the
+     * list of preferences. This can also be called when this
+     * Preference has been removed from a group that was attached
+     * to the list of preferences.
+     */
+    public void onDetached() {
+        unregisterDependency();
+    }
+
     private void registerDependency() {
 
         if (TextUtils.isEmpty(mDependencyKey)) return;
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
index bed7f7e..10a0753 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
@@ -17,6 +17,7 @@
 package android.support.v7.preference;
 
 import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
 import android.util.AttributeSet;
 
 /**
@@ -43,7 +44,8 @@
     }
 
     public PreferenceCategory(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.preferenceCategoryStyle);
+        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceCategoryStyle,
+                android.R.attr.preferenceCategoryStyle));
     }
 
     public PreferenceCategory(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
index 23ba56c..549d9be 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -257,10 +257,10 @@
 
         final View view = themedInflater.inflate(mLayoutResId, container, false);
 
-        final View rawListContainer = view.findViewById(R.id.list_container);
+        final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
         if (!(rawListContainer instanceof ViewGroup)) {
-            throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
-                    + "that is not a ViewGroup class");
+            throw new RuntimeException("Content has view with id attribute "
+                    + "'android.R.id.list_container' that is not a ViewGroup class");
         }
 
         final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -309,14 +309,19 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
 
         if (mHavePrefs) {
             bindPreferences();
         }
 
         mInitDone = true;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
 
         if (savedInstanceState != null) {
             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -345,9 +350,12 @@
 
     @Override
     public void onDestroyView() {
-        mList = null;
         mHandler.removeCallbacks(mRequestFocus);
         mHandler.removeMessages(MSG_BIND_PREFERENCES);
+        if (mHavePrefs) {
+            unbindPreferences();
+        }
+        mList = null;
         super.onDestroyView();
     }
 
@@ -514,6 +522,14 @@
         onBindPreferences();
     }
 
+    private void unbindPreferences() {
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        if (preferenceScreen != null) {
+            preferenceScreen.onDetached();
+        }
+        onUnbindPreferences();
+    }
+
     /** @hide */
     protected void onBindPreferences() {
     }
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
index a1ead97..2247962 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
@@ -233,6 +233,9 @@
                     mHandler.removeCallbacks(mClearRecycleCacheRunnable);
                     mHandler.post(mClearRecycleCacheRunnable);
                 }
+                if (mAttachedToHierarchy) {
+                    preference.onDetached();
+                }
             }
 
             return success;
@@ -336,11 +339,17 @@
     }
 
     @Override
-    protected void onPrepareForRemoval() {
-        super.onPrepareForRemoval();
+    public void onDetached() {
+        super.onDetached();
 
         // We won't be attached to the activity anymore
         mAttachedToHierarchy = false;
+
+        // Dispatch to all contained preferences
+        final int preferenceCount = getPreferenceCount();
+        for (int i = 0; i < preferenceCount; i++) {
+            getPreference(i).onDetached();
+        }
     }
 
     @Override
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
index d9f3dd6..807a4d3 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.preference;
 
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
@@ -63,8 +65,6 @@
 
     private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
 
-    private volatile boolean mIsSyncing = false;
-
     private Handler mHandler = new Handler();
 
     private Runnable mSyncRunnable = new Runnable() {
@@ -126,32 +126,21 @@
     }
 
     private void syncMyPreferences() {
-        synchronized(this) {
-            if (mIsSyncing) {
-                return;
-            }
+        final List<Preference> fullPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
+        flattenPreferenceGroup(fullPreferenceList, mPreferenceGroup);
 
-            mIsSyncing = true;
-        }
-
-        List<Preference> newPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
-        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
-        mPreferenceListInternal = newPreferenceList;
-
-        mPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
+        final List<Preference> visiblePreferenceList = new ArrayList<>(fullPreferenceList.size());
         // Copy only the visible preferences to the active list
-        for (final Preference preference : mPreferenceListInternal) {
+        for (final Preference preference : fullPreferenceList) {
             if (preference.isVisible()) {
-                mPreferenceList.add(preference);
+                visiblePreferenceList.add(preference);
             }
         }
 
+        mPreferenceList = visiblePreferenceList;
+        mPreferenceListInternal = fullPreferenceList;
+
         notifyDataSetChanged();
-
-        synchronized(this) {
-            mIsSyncing = false;
-            notifyAll();
-        }
     }
 
     private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
@@ -276,8 +265,18 @@
     public PreferenceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         final PreferenceLayout pl = mPreferenceLayouts.get(viewType);
         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        TypedArray a
+                = parent.getContext().obtainStyledAttributes(null, R.styleable.BackgroundStyle);
+        Drawable background
+                = a.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground);
+        if (background == null) {
+            background = parent.getContext().getResources()
+                    .getDrawable(android.R.drawable.list_selector_background);
+        }
+        a.recycle();
 
         final View view = inflater.inflate(pl.resId, parent, false);
+        view.setBackgroundDrawable(background);
 
         final ViewGroup widgetFrame = (ViewGroup) view.findViewById(android.R.id.widget_frame);
         if (widgetFrame != null) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceManager.java b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
index 0c3e65f..a921e22 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceManager.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
@@ -18,7 +18,9 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.content.SharedPreferencesCompat;
+import android.support.v4.os.BuildCompat;
 
 /**
  * Used to help create {@link Preference} hierarchies
@@ -76,6 +78,11 @@
      */
     private int mSharedPreferencesMode;
 
+    private static final int STORAGE_DEFAULT = 0;
+    private static final int STORAGE_DEVICE_PROTECTED = 1;
+
+    private int mStorage = STORAGE_DEFAULT;
+
     /**
      * The {@link PreferenceScreen} at the root of the preference hierarchy.
      */
@@ -185,6 +192,50 @@
     }
 
     /**
+     * Sets the storage location used internally by this class to be the default
+     * provided by the hosting {@link Context}.
+     */
+    public void setStorageDefault() {
+        if (BuildCompat.isAtLeastN()) {
+            mStorage = STORAGE_DEFAULT;
+            mSharedPreferences = null;
+        }
+    }
+
+    /**
+     * Explicitly set the storage location used internally by this class to be
+     * device-protected storage.
+     * <p>
+     * On devices with direct boot, data stored in this location is encrypted
+     * with a key tied to the physical device, and it can be accessed
+     * immediately after the device has booted successfully, both
+     * <em>before and after</em> the user has authenticated with their
+     * credentials (such as a lock pattern or PIN).
+     * <p>
+     * Because device-protected data is available without user authentication,
+     * you should carefully limit the data you store using this Context. For
+     * example, storing sensitive authentication tokens or passwords in the
+     * device-protected area is strongly discouraged.
+     * <p>
+     * Prior to {@link BuildCompat#isAtLeastN()} this method has no effect,
+     * since device-protected storage is not available.
+     *
+     * @see Context#createDeviceProtectedStorageContext()
+     */
+    public void setStorageDeviceProtected() {
+        if (BuildCompat.isAtLeastN()) {
+            mStorage = STORAGE_DEVICE_PROTECTED;
+            mSharedPreferences = null;
+        }
+    }
+
+    /** @removed */
+    @Deprecated
+    public void setStorageDeviceEncrypted() {
+        setStorageDeviceProtected();
+    }
+
+    /**
      * Gets a SharedPreferences instance that preferences managed by this will
      * use.
      *
@@ -193,7 +244,17 @@
      */
     public SharedPreferences getSharedPreferences() {
         if (mSharedPreferences == null) {
-            mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
+            final Context storageContext;
+            switch (mStorage) {
+                case STORAGE_DEVICE_PROTECTED:
+                    storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext);
+                    break;
+                default:
+                    storageContext = mContext;
+                    break;
+            }
+
+            mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
                     mSharedPreferencesMode);
         }
 
@@ -238,6 +299,9 @@
      */
     public boolean setPreferences(PreferenceScreen preferenceScreen) {
         if (preferenceScreen != mPreferenceScreen) {
+            if (mPreferenceScreen != null) {
+                mPreferenceScreen.onDetached();
+            }
             mPreferenceScreen = preferenceScreen;
             return true;
         }
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
index 4efa58e..cbabf21 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
@@ -17,6 +17,7 @@
 package android.support.v7.preference;
 
 import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
 import android.util.AttributeSet;
 
 /**
@@ -81,7 +82,8 @@
      * @hide-
      */
     public PreferenceScreen(Context context, AttributeSet attrs) {
-        super(context, attrs, R.attr.preferenceScreenStyle);
+        super(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceScreenStyle,
+                android.R.attr.preferenceScreenStyle));
     }
 
     @Override
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
index 2b604bd..97a907a 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
@@ -39,6 +39,8 @@
         mCachedViews.put(android.R.id.summary, itemView.findViewById(android.R.id.summary));
         mCachedViews.put(android.R.id.icon, itemView.findViewById(android.R.id.icon));
         mCachedViews.put(R.id.icon_frame, itemView.findViewById(R.id.icon_frame));
+        mCachedViews.put(AndroidResources.ANDROID_R_ICON_FRAME,
+                itemView.findViewById(AndroidResources.ANDROID_R_ICON_FRAME));
     }
 
     /**
diff --git a/v7/recyclerview/Android.mk b/v7/recyclerview/Android.mk
index 9a0e502..1a0819c 100644
--- a/v7/recyclerview/Android.mk
+++ b/v7/recyclerview/Android.mk
@@ -26,6 +26,7 @@
 LOCAL_AAPT_FLAGS := \
 	--auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Here is the final static library that apps can link against.
@@ -41,6 +42,7 @@
         android-support-v4 \
         android-support-annotations \
         android-support-v7-recyclerview-res
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 66afe6d..b377e70 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -1564,7 +1564,7 @@
      *
      * @return The horizontal offset of the scrollbar's thumb
      * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
-     * (RecyclerView.Adapter)
+     * (RecyclerView.State)
      */
     @Override
     public int computeHorizontalScrollOffset() {
@@ -1637,7 +1637,7 @@
      *
      * @return The vertical offset of the scrollbar's thumb
      * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
-     * (RecyclerView.Adapter)
+     * (RecyclerView.State)
      */
     @Override
     public int computeVerticalScrollOffset() {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
index 2e3c881..f8f6ecf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
@@ -27,10 +27,18 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(0, 0);
+
         mContainer = new TestedFrameLayout(this);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         setContentView(mContainer);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     }
+
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
 }
diff --git a/v8/renderscript/Android.mk b/v8/renderscript/Android.mk
index 520c8a1..17e06c0 100644
--- a/v8/renderscript/Android.mk
+++ b/v8/renderscript/Android.mk
@@ -24,15 +24,11 @@
 LOCAL_CFLAGS += -std=c++11
 
 LOCAL_MODULE := android-support-v8-renderscript
-LOCAL_SDK_VERSION := 22
+LOCAL_SDK_VERSION := 23
 LOCAL_SRC_FILES := $(call all-java-files-under, java/src)
 LOCAL_JAVA_LIBRARIES := android-support-annotations
 
-# Disable JACK when RSTest_Compatlib is used for updating RS prebuilts.
-ifneq (,$(UPDATE_RS_PREBUILTS_DISABLE_JACK))
-LOCAL_JACK_ENABLED := disabled
-endif
-
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
index 03244ff..c8a8854 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
@@ -19,6 +19,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.nio.ByteBuffer;
+
 import android.content.res.Resources;
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
@@ -61,8 +63,10 @@
     Type mType;
     Bitmap mBitmap;
     int mUsage;
-    Allocation mAdaptedAllocation;
     int mSize;
+    Allocation mAdaptedAllocation;
+    ByteBuffer mByteBuffer = null;
+    long mByteBufferStride = 0;
 
     boolean mConstrainedLOD;
     boolean mConstrainedFace;
@@ -468,6 +472,56 @@
     }
 
     /**
+     * @hide
+     * Get the ByteBuffer pointing to the raw data associated with Allocation.
+     * Note: The ByteBuffer will be Read-Only for devices before Lollopop (API 21).
+     */
+    public ByteBuffer getByteBuffer() {
+        int xBytesSize = mType.getX() * mType.getElement().getBytesSize();
+        // When running on devices before L, we need to construct the ByteBuffer
+        // and explicitly copy the data from the allocation to it.
+        if (mRS.getDispatchAPILevel() < 21) {
+            byte[] data = null;
+            if (mType.getZ() > 0) {
+                // TODO: add support for 3D allocations.
+                return null;
+            } else if (mType.getY() > 0) {
+                // 2D Allocation
+                data = new byte[xBytesSize * mType.getY()];
+                copy2DRangeToUnchecked(0, 0, mType.getX(), mType.getY(), data,
+                                       Element.DataType.SIGNED_8, xBytesSize * mType.getY());
+            } else {
+                // 1D Allocation
+                data = new byte[xBytesSize];
+                copy1DRangeToUnchecked(0, mType.getX(), data);
+            }
+            ByteBuffer bBuffer = ByteBuffer.wrap(data).asReadOnlyBuffer();
+            mByteBufferStride = xBytesSize;
+            return bBuffer;
+        }
+        // Create a new ByteBuffer if it is not initialized or using IO_INPUT.
+        if (mByteBuffer == null || (mUsage & USAGE_IO_INPUT) != 0) {
+            mByteBuffer = mRS.nAllocationGetByteBuffer(getID(mRS), xBytesSize, mType.getY(), mType.getZ());
+        }
+        return mByteBuffer;
+    }
+
+    /**
+     * @hide
+     * Get the Stride of raw data associated with this 2D Allocation.
+     */
+    public long getStride() {
+        if (mByteBufferStride ==0) {
+            if (mRS.getDispatchAPILevel() > 21) {
+                mByteBufferStride = mRS.nAllocationGetStride(getID(mRS));
+            } else {
+                mByteBufferStride = mType.getX() * mType.getElement().getBytesSize();
+            }
+        }
+        return mByteBufferStride;
+    }
+
+    /**
      * Receive the latest input into the Allocation. This operation
      * is only valid if {@link #USAGE_IO_INPUT} is set on the Allocation.
      *
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 5dcb50b..6fc9d5d 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -20,6 +20,8 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.ArrayList;
+import java.nio.ByteBuffer;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -30,7 +32,6 @@
 import android.os.Process;
 import android.util.Log;
 import android.view.Surface;
-import java.util.ArrayList;
 
 /**
  * This class provides access to a RenderScript context, which controls RenderScript
@@ -49,13 +50,18 @@
     @SuppressWarnings({"UnusedDeclaration", "deprecation"})
     static final boolean LOG_ENABLED = false;
     static final int SUPPORT_LIB_API = 23;
+    static final int SUPPORT_LIB_VERSION = 2301;
 
     static private ArrayList<RenderScript> mProcessContextList = new ArrayList<RenderScript>();
     private boolean mIsProcessContext = false;
+    private boolean mEnableMultiInput = false;
+    // TODO: Update to set to true at the correct API level when reduce is added.
+    private boolean mEnableReduce = false;
+    private int mDispatchAPILevel = 0;
+
     private int mContextFlags = 0;
     private int mContextSdkVersion = 0;
 
-
     private Context mApplicationContext;
     private String mNativeLibDir;
 
@@ -97,7 +103,7 @@
     static Object lock = new Object();
 
     // Non-threadsafe functions.
-    native boolean nLoadSO(boolean useNative, int deviceApi);
+    native boolean nLoadSO(boolean useNative, int deviceApi, String libPath);
     native boolean nLoadIOSO();
     native long nDeviceCreate();
     native void nDeviceDestroy(long dev);
@@ -119,6 +125,9 @@
      */
     public static final int CREATE_FLAG_NONE = 0x0000;
 
+    int getDispatchAPILevel() {
+        return mDispatchAPILevel;
+    }
 
     boolean isUseNative() {
         return useNative;
@@ -165,49 +174,42 @@
 
             if (sNative == 1) {
                 // Workarounds that may disable thunking go here
-                ApplicationInfo info = null;
+                ApplicationInfo info;
                 try {
                     info = ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(),
                                                                       PackageManager.GET_META_DATA);
                 } catch (PackageManager.NameNotFoundException e) {
                     // assume no workarounds needed
+                    return true;
+                }
+                long minorVersion = 0;
+
+                // load minorID from reflection
+                try {
+                    Class<?> javaRS = Class.forName("android.renderscript.RenderScript");
+                    Method getMinorID = javaRS.getDeclaredMethod("getMinorID");
+                    minorVersion = ((java.lang.Long)getMinorID.invoke(null)).longValue();
+                } catch (Exception e) {
+                    // minor version remains 0 on devices with no possible WARs
                 }
 
-                if (info != null) {
-                    long minorVersion = 0;
-                    // load minorID from reflection
-                    try {
-                        Class<?> javaRS = Class.forName("android.renderscript.RenderScript");
-                        if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
-                            Method getMinorID = javaRS.getDeclaredMethod("getMinorID");
-                            minorVersion = ((java.lang.Long)getMinorID.invoke(null)).longValue();
-                        } else {
-                            //For M+
-                            Method getMinorVersion = javaRS.getDeclaredMethod("getMinorVersion");
-                            minorVersion = ((java.lang.Long)getMinorVersion.invoke(null)).longValue();
-                        }
-                    } catch (Exception e) {
-                        // minor version remains 0 on devices with no possible WARs
-                    }
-
-                    if (info.metaData != null) {
-                        // asynchronous teardown: minor version 1+
-                        if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableAsyncTeardown") == true) {
-                            if (minorVersion == 0) {
-                                sNative = 0;
-                            }
-                        }
-
-                        // blur issues on some drivers with 4.4
-                        if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableBlurWorkaround") == true) {
-                            if (android.os.Build.VERSION.SDK_INT <= 19) {
-                                //android.util.Log.e("rs", "war on");
-                                sNative = 0;
-                            }
+                if (info.metaData != null) {
+                    // asynchronous teardown: minor version 1+
+                    if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableAsyncTeardown") == true) {
+                        if (minorVersion == 0) {
+                            sNative = 0;
                         }
                     }
-                    // end of workarounds
+
+                    // blur issues on some drivers with 4.4
+                    if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableBlurWorkaround") == true) {
+                        if (android.os.Build.VERSION.SDK_INT <= 19) {
+                            //android.util.Log.e("rs", "war on");
+                            sNative = 0;
+                        }
+                    }
                 }
+                // end of workarounds
             }
         }
 
@@ -435,7 +437,16 @@
         validate();
         rsnAllocationIoReceive(mContext, alloc);
     }
-
+    native ByteBuffer rsnAllocationGetByteBuffer(long con, long alloc, int xBytesSize, int dimY, int dimZ);
+    synchronized ByteBuffer nAllocationGetByteBuffer(long alloc, int xBytesSize, int dimY, int dimZ) {
+        validate();
+        return rsnAllocationGetByteBuffer(mContext, alloc, xBytesSize, dimY, dimZ);
+    }
+    native long rsnAllocationGetStride(long con, long alloc);
+    synchronized long nAllocationGetStride(long alloc) {
+        validate();
+        return rsnAllocationGetStride(mContext, alloc);
+    }
 
     native void rsnAllocationGenerateMipmaps(long con, long alloc);
     synchronized void nAllocationGenerateMipmaps(long alloc) {
@@ -549,7 +560,7 @@
         validate();
         rsnAllocationRead1D(mContext, id, off, mip, count, d, sizeBytes, dt.mID, mSize, usePadding);
     }
-    
+
     /*
     native void rsnAllocationElementRead(long con,long id, int xoff, int yoff, int zoff,
                                          int mip, int compIdx, byte[] d, int sizeBytes);
@@ -651,6 +662,39 @@
         }
     }
 
+    native void rsnScriptForEach(long con, long id, int slot, long[] ains,
+                                 long aout, byte[] params, int[] limits);
+
+    synchronized void nScriptForEach(long id, int slot, long[] ains, long aout,
+                                     byte[] params, int[] limits) {
+        if (!mEnableMultiInput) {
+            Log.e(LOG_TAG, "Multi-input kernels are not supported, please change targetSdkVersion to >= 23");
+            throw new RSRuntimeException("Multi-input kernels are not supported before API 23)");
+        }
+        validate();
+        rsnScriptForEach(mContext, id, slot, ains, aout, params, limits);
+    }
+
+    native void rsnScriptReduce(long con, long id, int slot, long ain, long aout,
+                                int[] limits);
+    synchronized void nScriptReduce(long id, int slot, long ain, long aout, int[] limits) {
+        if (!mEnableReduce) {
+            // TODO: Update to include the API level when reduce is added.
+            Log.e(LOG_TAG, "Reduce kernels are not supported");
+            throw new RSRuntimeException("Reduce kernels are not supported");
+        }
+        validate();
+        rsnScriptReduce(mContext, id, slot, ain, aout, limits);
+    }
+
+    native void rsnScriptReduceNew(long con, long id, int slot, long[] ains,
+                                   long aout, int[] limits);
+    synchronized void nScriptReduceNew(long id, int slot, long ains[], long aout,
+                                       int[] limits) {
+        validate();
+        rsnScriptReduceNew(mContext, id, slot, ains, aout, limits);
+    }
+
     native void rsnScriptInvokeV(long con, long id, int slot, byte[] params, boolean mUseInc);
     synchronized void nScriptInvokeV(long id, int slot, byte[] params, boolean mUseInc) {
         validate();
@@ -749,7 +793,7 @@
                     Log.e(LOG_TAG, "Error loading RS Compat library for Incremental Intrinsic Support: " + e);
                     throw new RSRuntimeException("Error loading RS Compat library for Incremental Intrinsic Support: " + e);
                 }
-                if (!nIncLoadSO(SUPPORT_LIB_API)) {
+                if (!nIncLoadSO(SUPPORT_LIB_API, mNativeLibDir + "/libRSSupport.so")) {
                     throw new RSRuntimeException("Error loading libRSSupport library for Incremental Intrinsic Support");
                 }
                 mIncLoaded = true;
@@ -945,7 +989,7 @@
 
 // Additional Entry points For inc libRSSupport
 
-    native boolean nIncLoadSO(int deviceApi);
+    native boolean nIncLoadSO(int deviceApi, String libPath);
     native long nIncDeviceCreate();
     native void nIncDeviceDestroy(long dev);
     // Methods below are wrapped to protect the non-threadsafe
@@ -996,10 +1040,10 @@
         validate();
         return rsnIncTypeCreate(mIncCon, eid, x, y, z, mips, faces, yuv);
     }
-    native long  rsnIncAllocationCreateTyped(long con, long incCon, long alloc, long type);
-    synchronized long nIncAllocationCreateTyped(long alloc, long type) {
+    native long  rsnIncAllocationCreateTyped(long con, long incCon, long alloc, long type, int xBytesSize);
+    synchronized long nIncAllocationCreateTyped(long alloc, long type, int xBytesSize) {
         validate();
-        return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type);
+        return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type, xBytesSize);
     }
 
     long     mDev;
@@ -1185,6 +1229,14 @@
         }
     }
 
+    void validateObject(BaseObj o) {
+        if (o != null) {
+            if (o.mRS != this) {
+                throw new RSIllegalArgumentException("Attempting to use an object across contexts.");
+            }
+        }
+    }
+
     void validate() {
         if (mContext == 0) {
             throw new RSInvalidStateException("Calling RS with no Context active.");
@@ -1293,7 +1345,10 @@
         mContextType = ContextType.NORMAL;
         if (ctx != null) {
             mApplicationContext = ctx.getApplicationContext();
-            mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
+            // Only set mNativeLibDir for API 9+.
+            if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.FROYO) {
+                mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
+            }
         }
         mIncDev = 0;
         mIncCon = 0;
@@ -1339,12 +1394,19 @@
                     sUseGCHooks = false;
                 }
                 try {
-                    System.loadLibrary("rsjni");
+                    // For API 9 - 22, always use the absolute path of librsjni.so
+                    // http://b/25226912
+                    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+                        rs.mNativeLibDir != null) {
+                        System.load(rs.mNativeLibDir + "/librsjni.so");
+                    } else {
+                        System.loadLibrary("rsjni");
+                    }
                     sInitialized = true;
                     sPointerSize = rsnSystemGetPointerSize();
                 } catch (UnsatisfiedLinkError e) {
                     Log.e(LOG_TAG, "Error loading RS jni library: " + e);
-                    throw new RSRuntimeException("Error loading RS jni library: " + e);
+                    throw new RSRuntimeException("Error loading RS jni library: " + e + " Support lib API: " + SUPPORT_LIB_VERSION);
                 }
             }
         }
@@ -1359,24 +1421,39 @@
             useIOlib = true;
         }
 
+        // The target API level used to init dispatchTable.
         int dispatchAPI = sdkVersion;
         if (sdkVersion < android.os.Build.VERSION.SDK_INT) {
             // If the device API is higher than target API level, init dispatch table based on device API.
             dispatchAPI = android.os.Build.VERSION.SDK_INT;
         }
-        if (!rs.nLoadSO(useNative, dispatchAPI)) {
+
+        String rssupportPath = null;
+        // For API 9 - 22, always use the absolute path of libRSSupport.so
+        // http://b/25226912
+        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+            rs.mNativeLibDir != null) {
+            rssupportPath = rs.mNativeLibDir + "/libRSSupport.so";
+        }
+        if (!rs.nLoadSO(useNative, dispatchAPI, rssupportPath)) {
             if (useNative) {
                 android.util.Log.v(LOG_TAG, "Unable to load libRS.so, falling back to compat mode");
                 useNative = false;
             }
             try {
-                System.loadLibrary("RSSupport");
+                if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M &&
+                    rs.mNativeLibDir != null) {
+                    System.load(rssupportPath);
+                } else {
+                    System.loadLibrary("RSSupport");
+                }
             } catch (UnsatisfiedLinkError e) {
-                Log.e(LOG_TAG, "Error loading RS Compat library: " + e);
-                throw new RSRuntimeException("Error loading RS Compat library: " + e);
+                Log.e(LOG_TAG, "Error loading RS Compat library: " + e + " Support lib version: " + SUPPORT_LIB_VERSION);
+                throw new RSRuntimeException("Error loading RS Compat library: " + e + " Support lib version: " + SUPPORT_LIB_VERSION);
             }
-            if (!rs.nLoadSO(false, dispatchAPI)) {
-                throw new RSRuntimeException("Error loading libRSSupport library");
+            if (!rs.nLoadSO(false, dispatchAPI, rssupportPath)) {
+                Log.e(LOG_TAG, "Error loading RS Compat library: nLoadSO() failed; Support lib version: " + SUPPORT_LIB_VERSION);
+                throw new RSRuntimeException("Error loading libRSSupport library, Support lib version: " + SUPPORT_LIB_VERSION);
             }
         }
 
@@ -1392,11 +1469,24 @@
             }
         }
 
+        // For old APIs with dlopen bug, need to load blas lib in Java first.
+        // Only try load to blasV8 when the desired API level includes IntrinsicBLAS.
+        if (dispatchAPI >= 23) {
+            // Enable multi-input kernels only when diapatchAPI is M+.
+            rs.mEnableMultiInput = true;
+            try {
+                System.loadLibrary("blasV8");
+            } catch (UnsatisfiedLinkError e) {
+                Log.v(LOG_TAG, "Unable to load BLAS lib, ONLY BNNM will be supported: " + e);
+            }
+        }
+
         rs.mDev = rs.nDeviceCreate();
         rs.mContext = rs.nContextCreate(rs.mDev, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
         rs.mContextType = ct;
         rs.mContextFlags = flags;
         rs.mContextSdkVersion = sdkVersion;
+        rs.mDispatchAPILevel = dispatchAPI;
         if (rs.mContext == 0) {
             throw new RSDriverException("Failed to create RS context.");
         }
@@ -1595,9 +1685,21 @@
         }
         nContextDeinitToClient(mContext);
         mMessageThread.mRun = false;
-        try {
-            mMessageThread.join();
-        } catch(InterruptedException e) {
+
+        // Wait for mMessageThread to join.  Try in a loop, in case this thread gets interrupted
+        // during the wait.  If interrupted, set the "interrupted" status of the current thread.
+        boolean hasJoined = false, interrupted = false;
+        while (!hasJoined) {
+            try {
+                mMessageThread.join();
+                hasJoined = true;
+            } catch (InterruptedException e) {
+                interrupted = true;
+            }
+        }
+        if (interrupted) {
+            Log.v(LOG_TAG, "Interrupted during wait for MessageThread to join");
+            Thread.currentThread().interrupt();
         }
 
         nContextDestroy();
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/Script.java b/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
index cacd146..814622e 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/Script.java
@@ -44,9 +44,11 @@
         long dInType = 0;
         long dummyAlloc = 0;
         if (ain != null) {
-            dInElement = ain.getType().getElement().getDummyElement(mRS);
-            dInType = ain.getType().getDummyType(mRS, dInElement);
-            dummyAlloc = mRS.nIncAllocationCreateTyped(ain.getID(mRS), dInType);
+            Type inType = ain.getType();
+            dInElement = inType.getElement().getDummyElement(mRS);
+            dInType = inType.getDummyType(mRS, dInElement);
+            int xBytesSize = inType.getX() * inType.getElement().getBytesSize();
+            dummyAlloc = mRS.nIncAllocationCreateTyped(ain.getID(mRS), dInType, xBytesSize);
             ain.setIncAllocID(dummyAlloc);
         }
 
@@ -317,6 +319,141 @@
     /**
      * Only intended for use by generated reflected code.
      *
+     * @hide
+     */
+    protected void forEach(int slot, Allocation[] ains, Allocation aout,
+                           FieldPacker v) {
+        forEach(slot, ains, aout, v, null);
+    }
+
+    /**
+     * Only intended for use by generated reflected code.
+     *
+     * @hide
+     */
+    protected void forEach(int slot, Allocation[] ains, Allocation aout,
+                           FieldPacker v, LaunchOptions sc) {
+        // TODO: Is this necessary if nScriptForEach calls validate as well?
+        mRS.validate();
+        if (ains != null) {
+            for (Allocation ain : ains) {
+                mRS.validateObject(ain);
+            }
+        }
+        mRS.validateObject(aout);
+
+        if (ains == null && aout == null) {
+            throw new RSIllegalArgumentException(
+                "At least one of ain or aout is required to be non-null.");
+        }
+
+        long[] in_ids;
+        if (ains != null) {
+            in_ids = new long[ains.length];
+            for (int index = 0; index < ains.length; ++index) {
+                in_ids[index] = ains[index].getID(mRS);
+            }
+        } else {
+            in_ids = null;
+        }
+
+        long out_id = 0;
+        if (aout != null) {
+            out_id = aout.getID(mRS);
+        }
+
+        byte[] params = null;
+        if (v != null) {
+            params = v.getData();
+        }
+
+        int[] limits = null;
+        if (sc != null) {
+            limits = new int[6];
+
+            limits[0] = sc.xstart;
+            limits[1] = sc.xend;
+            limits[2] = sc.ystart;
+            limits[3] = sc.yend;
+            limits[4] = sc.zstart;
+            limits[5] = sc.zend;
+        }
+
+        mRS.nScriptForEach(getID(mRS), slot, in_ids, out_id, params, limits);
+    }
+
+    /**
+     * Only intended for use by generated reflected code.  (Simple reduction)
+     *
+     * @hide
+     */
+    protected void reduce(int slot, Allocation ain, Allocation aout, LaunchOptions sc) {
+        mRS.validate();
+        mRS.validateObject(ain);
+        mRS.validateObject(aout);
+
+        if (ain == null || aout == null) {
+            throw new RSIllegalArgumentException(
+                "Both ain and aout are required to be non-null.");
+        }
+
+        long in_id = ain.getID(mRS);
+        long out_id = aout.getID(mRS);
+
+        int[] limits = null;
+        if (sc != null) {
+            limits = new int[2];
+
+            limits[0] = sc.xstart;
+            limits[1] = sc.xend;
+        }
+
+        mRS.nScriptReduce(getID(mRS), slot, in_id, out_id, limits);
+    }
+
+    /**
+     * Only intended for use by generated reflected code.  (General reduction)
+     *
+     * @hide
+     */
+    protected void reduce(int slot, Allocation[] ains, Allocation aout, LaunchOptions sc) {
+        mRS.validate();
+        if (ains == null || ains.length < 1) {
+            throw new RSIllegalArgumentException(
+                "At least one input is required.");
+        }
+        if (aout == null) {
+            throw new RSIllegalArgumentException(
+                "aout is required to be non-null.");
+        }
+        for (Allocation ain : ains) {
+            mRS.validateObject(ain);
+        }
+
+        long[] in_ids = new long[ains.length];
+        for (int index = 0; index < ains.length; ++index) {
+            in_ids[index] = ains[index].getID(mRS);
+        }
+        long out_id = aout.getID(mRS);
+
+        int[] limits = null;
+        if (sc != null) {
+            limits = new int[6];
+
+            limits[0] = sc.xstart;
+            limits[1] = sc.xend;
+            limits[2] = sc.ystart;
+            limits[3] = sc.yend;
+            limits[4] = sc.zstart;
+            limits[5] = sc.zend;
+        }
+
+        mRS.nScriptReduceNew(getID(mRS), slot, in_ids, out_id, limits);
+    }
+
+    /**
+     * Only intended for use by generated reflected code.
+     *
      * @param index
      * @param v
      */
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
index 15e8b55..065712a 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
@@ -307,10 +307,10 @@
                     value = ((Long)obj).longValue();
                     size = 8;
                 } else if (obj instanceof Float) {
-                    value = ((Float)obj).longValue();
+                    value = Float.floatToRawIntBits(((Float)obj).floatValue());
                     size = 4;
                 } else if (obj instanceof Double) {
-                    value = ((Double)obj).longValue();
+                    value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
                     size = 8;
                 }
             }
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
index a5b046b..2d2923e 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicBlur.java
@@ -97,7 +97,7 @@
      *             type.
      */
     public void forEach(Allocation aout) {
-        forEach(0, null, aout, null);
+        forEach(0, (Allocation) null, aout, null);
     }
 
     /**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
index ea45461..0d398b7 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve3x3.java
@@ -115,7 +115,7 @@
      *             type.
      */
     public void forEach(Allocation aout) {
-        forEach(0, null, aout, null);
+        forEach(0, (Allocation) null, aout, null);
     }
 
     /**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
index bcd37f1..9dd9cba 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -117,7 +117,7 @@
      *             type.
      */
     public void forEach(Allocation aout) {
-        forEach(0, null, aout, null);
+        forEach(0, (Allocation) null, aout, null);
     }
 
     /**
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
index f3d0df7..6c84020 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptIntrinsicYuvToRGB.java
@@ -74,7 +74,7 @@
      *             type.
      */
     public void forEach(Allocation aout) {
-        forEach(0, null, aout, null);
+        forEach(0, (Allocation) null, aout, null);
     }
 
     /**
diff --git a/v8/renderscript/jni/Android.mk b/v8/renderscript/jni/Android.mk
index d2af4a9..2e8fa62 100644
--- a/v8/renderscript/jni/Android.mk
+++ b/v8/renderscript/jni/Android.mk
@@ -21,7 +21,8 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_LDLIBS += -landroid
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_LDFLAGS += -ldl -Wl,--exclude-libs,libc++_static.a
+LOCAL_NDK_STL_VARIANT := c++_static
 include $(BUILD_SHARED_LIBRARY)
 
 include $(CLEAR_VARS)
@@ -36,7 +37,6 @@
         libjnigraphics
 
 LOCAL_STATIC_LIBRARIES := \
-        libcutils \
         libRSDispatch
 
 LOCAL_C_INCLUDES += \
@@ -50,6 +50,7 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_REQUIRED_MODULES := libRSSupport
 
-LOCAL_LDFLAGS += -ldl
+LOCAL_LDFLAGS += -ldl -llog -Wl,--exclude-libs,libc++_static.a
+LOCAL_NDK_STL_VARIANT := c++_static
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/v8/renderscript/jni/android_renderscript_RenderScript.cpp b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
index 9d69a2d..ed8d5ee 100644
--- a/v8/renderscript/jni/android_renderscript_RenderScript.cpp
+++ b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
@@ -30,6 +30,8 @@
 
 //#define LOG_API ALOG
 #define LOG_API(...)
+#define LOG_ERR(...) __android_log_print(ANDROID_LOG_ERROR, "RenderScript JNI", __VA_ARGS__);
+#define RS_JNI_VERSION 2301
 
 #define NELEM(m) (sizeof(m) / sizeof((m)[0]))
 
@@ -269,23 +271,31 @@
 // Incremental Support lib
 static dispatchTable dispatchTabInc;
 
-static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative, jint targetApi) {
+static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative, jint targetApi, jstring libPath) {
     void* handle = NULL;
     if (useNative) {
         handle = dlopen("libRS.so", RTLD_LAZY | RTLD_LOCAL);
     } else {
-        handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+        // For API 9+, dlopen the full path of libRSSupport.
+        if (libPath != NULL) {
+            const char * libPathJni = _env->GetStringUTFChars(libPath, JNI_FALSE);
+            handle = dlopen(libPathJni, RTLD_LAZY | RTLD_LOCAL);
+            _env->ReleaseStringUTFChars(libPath, libPathJni);
+        } else {
+            handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+        }
     }
     if (handle == NULL) {
-        LOG_API("couldn't dlopen %s, %s", filename, dlerror());
+        LOG_ERR("couldn't dlopen %s; librsjni version: %d", dlerror(), RS_JNI_VERSION);
         return false;
     }
 
     if (loadSymbols(handle, dispatchTab, targetApi) == false) {
-        LOG_API("%s init failed!", filename);
+        LOG_ERR("Dispatch table init failed! librsjni version: %d", RS_JNI_VERSION);
+        dlclose(handle);
         return false;
     }
-    LOG_API("Successfully loaded %s", filename);
+    LOG_API("Successfully loaded runtime");
     return true;
 }
 
@@ -294,11 +304,11 @@
     void* handleIO = NULL;
     handleIO = dlopen("libRSSupportIO.so", RTLD_LAZY | RTLD_LOCAL);
     if (handleIO == NULL) {
-        LOG_API("Couldn't load libRSSupportIO.so");
+        LOG_ERR("Couldn't load libRSSupportIO.so, librsjni version: %d", RS_JNI_VERSION);
         return false;
     }
     if (loadIOSuppSyms(handleIO, ioDispatch) == false) {
-        LOG_API("libRSSupportIO init failed!");
+        LOG_ERR("libRSSupportIO init failed! librsjni version: %d", RS_JNI_VERSION);
         return false;
     }
     return true;
@@ -362,31 +372,30 @@
 
   size_t numValues, numDependencies;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
   RsClosure* depClosures;
   RsScriptFieldID* depFieldIDs;
 
   if (fieldIDs_length != values_length || values_length != sizes_length) {
-      LOG_API("Unmatched field IDs, values, and sizes in closure creation.");
+      LOG_ERR("Unmatched field IDs, values, and sizes in closure creation.");
       goto exit;
   }
 
   numValues = (size_t)fieldIDs_length;
 
   if (depClosures_length != depFieldIDs_length) {
-      LOG_API("Unmatched closures and field IDs for dependencies in closure creation.");
+      LOG_ERR("Unmatched closures and field IDs for dependencies in closure creation.");
       goto exit;
   }
 
   numDependencies = (size_t)depClosures_length;
 
   if (numDependencies > numValues) {
-      LOG_API("Unexpected number of dependencies in closure creation");
+      LOG_ERR("Unexpected number of dependencies in closure creation");
       goto exit;
   }
 
   if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) {
-      LOG_API("Too many arguments or globals in closure creation");
+      LOG_ERR("Too many arguments or globals in closure creation");
       goto exit;
   }
 
@@ -399,15 +408,6 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   depClosures = (RsClosure*)alloca(sizeof(RsClosure) * numDependencies);
   if (depClosures == nullptr) {
       goto exit;
@@ -428,7 +428,7 @@
 
   ret = (jlong)(uintptr_t)dispatchTab.ClosureCreate(
       (RsContext)con, (RsScriptKernelID)kernelID, (RsAllocation)returnValue,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues,
       depClosures, numDependencies,
       depFieldIDs, numDependencies);
@@ -461,17 +461,16 @@
 
   size_t numValues;
   RsScriptFieldID* fieldIDs;
-  uintptr_t* values;
 
   if (fieldIDs_length != values_length || values_length != sizes_length) {
-      LOG_API("Unmatched field IDs, values, and sizes in closure creation.");
+      LOG_ERR("Unmatched field IDs, values, and sizes in closure creation.");
       goto exit;
   }
 
   numValues = (size_t) fieldIDs_length;
 
   if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) {
-      LOG_API("Too many arguments or globals in closure creation");
+      LOG_ERR("Too many arguments or globals in closure creation");
       goto exit;
   }
 
@@ -484,18 +483,9 @@
     fieldIDs[i] = (RsScriptFieldID)jFieldIDs[i];
   }
 
-  values = (uintptr_t*)alloca(sizeof(uintptr_t) * numValues);
-  if (values == nullptr) {
-      goto exit;
-  }
-
-  for (size_t i = 0; i < numValues; i++) {
-    values[i] = (uintptr_t)jValues[i];
-  }
-
   ret = (jlong)(uintptr_t)dispatchTab.InvokeClosureCreate(
       (RsContext)con, (RsScriptInvokeID)invokeID, jParams, jParamLength,
-      fieldIDs, numValues, values, numValues,
+      fieldIDs, numValues, jValues, numValues,
       (int*)jSizes, numValues);
 
 exit:
@@ -511,15 +501,17 @@
 static void
 nClosureSetArg(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                jint index, jlong value, jint size) {
+  // Size is signed with -1 indicating the values is an Allocation
   dispatchTab.ClosureSetArg((RsContext)con, (RsClosure)closureID, (uint32_t)index,
-                  (uintptr_t)value, (size_t)size);
+                  (uintptr_t)value, size);
 }
 
 static void
 nClosureSetGlobal(JNIEnv *_env, jobject _this, jlong con, jlong closureID,
                   jlong fieldID, jlong value, jint size) {
+  // Size is signed with -1 indicating the values is an Allocation
   dispatchTab.ClosureSetGlobal((RsContext)con, (RsClosure)closureID,
-                     (RsScriptFieldID)fieldID, (uintptr_t)value, (size_t)size);
+                     (RsScriptFieldID)fieldID, (int64_t)value, size);
 }
 
 static long
@@ -536,7 +528,7 @@
   RsClosure* closures;
 
   if (numClosures > (jsize) RS_SCRIPT_GROUP_MAX_NUMBER_CLOSURES) {
-    LOG_API("Too many closures in script group");
+    LOG_ERR("Too many closures in script group");
     goto exit;
   }
 
@@ -912,7 +904,7 @@
     jint len = 0;
     if (data) {
         len = _env->GetArrayLength(data);
-        jint *ptr = _env->GetIntArrayElements(data, NULL);
+        ptr = _env->GetIntArrayElements(data, NULL);
     }
     LOG_API("nContextSendMessage, con(%p), id(%i), len(%i)", (RsContext)con, id, len);
     dispatchTab.ContextSendMessage((RsContext)con, id, (const uint8_t *)ptr, len * sizeof(int));
@@ -1546,6 +1538,7 @@
                                   NULL, 0, NULL, 0);
     }
 }
+
 static void
 nScriptForEachV(JNIEnv *_env, jobject _this, jlong con, jlong incCon,
                 jlong script, jint slot, jlong ain, jlong aout, jbyteArray params, jboolean mUseInc)
@@ -1627,6 +1620,263 @@
     _env->ReleaseByteArrayElements(params, ptr, JNI_ABORT);
 }
 
+static void
+nScriptForEachMulti(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+                    jlongArray ains, jlong aout, jbyteArray params,
+                    jintArray limits)
+{
+    LOG_API("nScriptForEach, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout);
+
+    jint   in_len = 0;
+    jlong *in_ptr = nullptr;
+
+    RsAllocation *in_allocs = nullptr;
+
+    if (ains != nullptr) {
+        in_len = _env->GetArrayLength(ains);
+        if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) {
+            LOG_ERR("Too many arguments in kernel launch.");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            return;
+        }
+
+        // TODO (b/20760800): Check in_ptr is not null
+        in_ptr = _env->GetLongArrayElements(ains, nullptr);
+        if (sizeof(RsAllocation) == sizeof(jlong)) {
+            in_allocs = (RsAllocation*)in_ptr;
+
+        } else {
+            // Convert from 64-bit jlong types to the native pointer type.
+
+            in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+            if (in_allocs == nullptr) {
+                LOG_ERR("Failed launching kernel for lack of memory.");
+                _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+                return;
+            }
+
+            for (int index = in_len; --index >= 0;) {
+                in_allocs[index] = (RsAllocation)in_ptr[index];
+            }
+        }
+    }
+
+    jint   param_len = 0;
+    jbyte *param_ptr = nullptr;
+
+    if (params != nullptr) {
+        param_len = _env->GetArrayLength(params);
+        param_ptr = _env->GetByteArrayElements(params, nullptr);
+    }
+
+    RsScriptCall sc, *sca = nullptr;
+    uint32_t sc_size = 0;
+
+    jint  limit_len = 0;
+    jint *limit_ptr = nullptr;
+
+    if (limits != nullptr) {
+        limit_len = _env->GetArrayLength(limits);
+        limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+
+        if (limit_len != 6) {
+            LOG_ERR("LaunchOptions cannot be recognized.");
+            goto exit;
+        }
+
+        sc.xStart     = limit_ptr[0];
+        sc.xEnd       = limit_ptr[1];
+        sc.yStart     = limit_ptr[2];
+        sc.yEnd       = limit_ptr[3];
+        sc.zStart     = limit_ptr[4];
+        sc.zEnd       = limit_ptr[5];
+        sc.strategy   = RS_FOR_EACH_STRATEGY_DONT_CARE;
+        sc.arrayStart = 0;
+        sc.arrayEnd = 0;
+        sc.array2Start = 0;
+        sc.array2End = 0;
+        sc.array3Start = 0;
+        sc.array3End = 0;
+        sc.array4Start = 0;
+        sc.array4End = 0;
+
+        sca = &sc;
+    }
+
+    dispatchTabInc.ScriptForEachMulti((RsContext)con, (RsScript)script, slot,
+                                      in_allocs, in_len, (RsAllocation)aout,
+                                      param_ptr, param_len, sca, sc_size);
+
+exit:
+
+    if (ains != nullptr) {
+        _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+    }
+
+    if (params != nullptr) {
+        _env->ReleaseByteArrayElements(params, param_ptr, JNI_ABORT);
+    }
+
+    if (limits != nullptr) {
+        _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+    }
+}
+
+static void
+nScriptReduce(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+              jlong ain, jlong aout, jintArray limits)
+{
+    LOG_API("nScriptReduce, con(%p), s(%p), slot(%i) ain(%" PRId64 ") aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ain, aout);
+
+    RsScriptCall sc, *sca = nullptr;
+    uint32_t sc_size = 0;
+
+    jint  limit_len = 0;
+    jint *limit_ptr = nullptr;
+
+    bool limitLengthValid = true;
+
+    // If the caller passed limits, reflect them in the RsScriptCall.
+    if (limits != nullptr) {
+        limit_len = _env->GetArrayLength(limits);
+        limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+
+        // We expect to be passed an array [x1, x2] which specifies
+        // the sub-range for a 1-dimensional reduction.
+        if (limit_len == 2) {
+            sc.xStart     = limit_ptr[0];
+            sc.xEnd       = limit_ptr[1];
+            sc.yStart     = 0;
+            sc.yEnd       = 0;
+            sc.zStart     = 0;
+            sc.zEnd       = 0;
+            sc.strategy   = RS_FOR_EACH_STRATEGY_DONT_CARE;
+            sc.arrayStart = 0;
+            sc.arrayEnd = 0;
+            sc.array2Start = 0;
+            sc.array2End = 0;
+            sc.array3Start = 0;
+            sc.array3End = 0;
+            sc.array4Start = 0;
+            sc.array4End = 0;
+
+            sca = &sc;
+            sc_size = sizeof(sc);
+        } else {
+            LOG_ERR("LaunchOptions cannot be recognized.");
+            limitLengthValid = false;
+        }
+    }
+
+    if (limitLengthValid) {
+        dispatchTab.ScriptReduce((RsContext)con, (RsScript)script, slot,
+                                 (RsAllocation)ain, (RsAllocation)aout,
+                                 sca, sc_size);
+    }
+
+    if (limits != nullptr) {
+        _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+    }
+}
+
+static void
+nScriptReduceNew(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot,
+                 jlongArray ains, jlong aout, jintArray limits)
+{
+    LOG_API("nScriptReduceNew, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout);
+
+    if (ains == nullptr) {
+        LOG_ERR("At least one input required.");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+    jint in_len = _env->GetArrayLength(ains);
+    if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) {
+        LOG_ERR("Too many arguments in kernel launch.");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+
+    jlong *in_ptr = _env->GetLongArrayElements(ains, nullptr);
+    if (in_ptr == nullptr) {
+        LOG_ERR("Failed to get Java array elements");
+        // TODO (b/20758983): Report back to Java and throw an exception
+        return;
+    }
+
+    RsAllocation *in_allocs = nullptr;
+    if (sizeof(RsAllocation) == sizeof(jlong)) {
+        in_allocs = (RsAllocation*)in_ptr;
+    } else {
+        // Convert from 64-bit jlong types to the native pointer type.
+
+        in_allocs = (RsAllocation*)alloca(in_len * sizeof(RsAllocation));
+        if (in_allocs == nullptr) {
+            LOG_ERR("Failed launching kernel for lack of memory.");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+            return;
+        }
+
+        for (int index = in_len; --index >= 0;) {
+            in_allocs[index] = (RsAllocation)in_ptr[index];
+        }
+    }
+
+    RsScriptCall sc, *sca = nullptr;
+    uint32_t sc_size = 0;
+
+    jint  limit_len = 0;
+    jint *limit_ptr = nullptr;
+
+    if (limits != nullptr) {
+        limit_len = _env->GetArrayLength(limits);
+        limit_ptr = _env->GetIntArrayElements(limits, nullptr);
+        if (limit_ptr == nullptr) {
+            LOG_ERR("Failed to get Java array elements");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+            return;
+        }
+
+        if (limit_len != 6) {
+            LOG_ERR("LaunchOptions cannot be recognized");
+            // TODO (b/20758983): Report back to Java and throw an exception
+            _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+            return;
+        }
+
+        sc.xStart     = limit_ptr[0];
+        sc.xEnd       = limit_ptr[1];
+        sc.yStart     = limit_ptr[2];
+        sc.yEnd       = limit_ptr[3];
+        sc.zStart     = limit_ptr[4];
+        sc.zEnd       = limit_ptr[5];
+        sc.strategy   = RS_FOR_EACH_STRATEGY_DONT_CARE;
+        sc.arrayStart = 0;
+        sc.arrayEnd = 0;
+        sc.array2Start = 0;
+        sc.array2End = 0;
+        sc.array3Start = 0;
+        sc.array3End = 0;
+        sc.array4Start = 0;
+        sc.array4End = 0;
+
+        sca = &sc;
+        sc_size = sizeof(sc);
+    }
+
+    dispatchTab.ScriptReduceNew((RsContext)con, (RsScript)script, slot,
+                                in_allocs, in_len, (RsAllocation)aout,
+                                sca, sc_size);
+
+    _env->ReleaseLongArrayElements(ains, in_ptr, JNI_ABORT);
+
+    if (limits != nullptr) {
+        _env->ReleaseIntArrayElements(limits, limit_ptr, JNI_ABORT);
+    }
+}
+
 // -----------------------------------
 
 static jlong
@@ -1728,42 +1978,75 @@
 {
     LOG_API("nScriptGroupCreate, con(%p)", (RsContext)con);
 
+    jlong id = 0;
+
+    RsScriptKernelID* kernelsPtr;
     jint kernelsLen = _env->GetArrayLength(_kernels);
     jlong *jKernelsPtr = _env->GetLongArrayElements(_kernels, nullptr);
-    RsScriptKernelID* kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
+
+    RsScriptKernelID* srcPtr;
+    jint srcLen = _env->GetArrayLength(_src);
+    jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
+
+    RsScriptKernelID* dstkPtr;
+    jint dstkLen = _env->GetArrayLength(_dstk);
+    jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
+
+    RsScriptKernelID* dstfPtr;
+    jint dstfLen = _env->GetArrayLength(_dstf);
+    jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
+
+    RsType* typesPtr;
+    jint typesLen = _env->GetArrayLength(_types);
+    jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
+
+    if (jKernelsPtr == nullptr) {
+        LOG_ERR("Failed to get Java array elements: kernels");
+        goto cleanup;
+    }
+    if (jSrcPtr == nullptr) {
+        LOG_ERR("Failed to get Java array elements: src");
+        goto cleanup;
+    }
+    if (jDstkPtr == nullptr) {
+        LOG_ERR("Failed to get Java array elements: dstk");
+        goto cleanup;
+    }
+    if (jDstfPtr == nullptr) {
+        LOG_ERR("Failed to get Java array elements: dstf");
+        goto cleanup;
+    }
+    if (jTypesPtr == nullptr) {
+        LOG_ERR("Failed to get Java array elements: types");
+        goto cleanup;
+    }
+
+    kernelsPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * kernelsLen);
     for(int i = 0; i < kernelsLen; ++i) {
         kernelsPtr[i] = (RsScriptKernelID)jKernelsPtr[i];
     }
 
-    jint srcLen = _env->GetArrayLength(_src);
-    jlong *jSrcPtr = _env->GetLongArrayElements(_src, nullptr);
-    RsScriptKernelID* srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
+    srcPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * srcLen);
     for(int i = 0; i < srcLen; ++i) {
         srcPtr[i] = (RsScriptKernelID)jSrcPtr[i];
     }
 
-    jint dstkLen = _env->GetArrayLength(_dstk);
-    jlong *jDstkPtr = _env->GetLongArrayElements(_dstk, nullptr);
-    RsScriptKernelID* dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
+    dstkPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstkLen);
     for(int i = 0; i < dstkLen; ++i) {
         dstkPtr[i] = (RsScriptKernelID)jDstkPtr[i];
     }
 
-    jint dstfLen = _env->GetArrayLength(_dstf);
-    jlong *jDstfPtr = _env->GetLongArrayElements(_dstf, nullptr);
-    RsScriptKernelID* dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
+    dstfPtr = (RsScriptKernelID*) malloc(sizeof(RsScriptKernelID) * dstfLen);
     for(int i = 0; i < dstfLen; ++i) {
         dstfPtr[i] = (RsScriptKernelID)jDstfPtr[i];
     }
 
-    jint typesLen = _env->GetArrayLength(_types);
-    jlong *jTypesPtr = _env->GetLongArrayElements(_types, nullptr);
-    RsType* typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
+    typesPtr = (RsType*) malloc(sizeof(RsType) * typesLen);
     for(int i = 0; i < typesLen; ++i) {
         typesPtr[i] = (RsType)jTypesPtr[i];
     }
 
-    jlong id = (jlong)(uintptr_t) dispatchTab.ScriptGroupCreate((RsContext)con,
+    id = (jlong)(uintptr_t) dispatchTab.ScriptGroupCreate((RsContext)con,
                                (RsScriptKernelID *)kernelsPtr, kernelsLen * sizeof(RsScriptKernelID),
                                (RsScriptKernelID *)srcPtr, srcLen * sizeof(RsScriptKernelID),
                                (RsScriptKernelID *)dstkPtr, dstkLen * sizeof(RsScriptKernelID),
@@ -1775,11 +2058,24 @@
     free(dstkPtr);
     free(dstfPtr);
     free(typesPtr);
-    _env->ReleaseLongArrayElements(_kernels, jKernelsPtr, 0);
-    _env->ReleaseLongArrayElements(_src, jSrcPtr, 0);
-    _env->ReleaseLongArrayElements(_dstk, jDstkPtr, 0);
-    _env->ReleaseLongArrayElements(_dstf, jDstfPtr, 0);
-    _env->ReleaseLongArrayElements(_types, jTypesPtr, 0);
+
+cleanup:
+    if (jKernelsPtr != nullptr) {
+        _env->ReleaseLongArrayElements(_kernels, jKernelsPtr, 0);
+    }
+    if (jSrcPtr != nullptr) {
+        _env->ReleaseLongArrayElements(_src, jSrcPtr, 0);
+    }
+    if (jDstkPtr != nullptr) {
+        _env->ReleaseLongArrayElements(_dstk, jDstkPtr, 0);
+    }
+    if (jDstfPtr != nullptr) {
+        _env->ReleaseLongArrayElements(_dstf, jDstfPtr, 0);
+    }
+    if (jTypesPtr != nullptr) {
+        _env->ReleaseLongArrayElements(_types, jTypesPtr, 0);
+    }
+
     return id;
 }
 
@@ -1831,19 +2127,34 @@
 
 // ---------------------------------------------------------------------------
 // For Incremental Intrinsic Support
-static jboolean nIncLoadSO(JNIEnv *_env, jobject _this, jint deviceApi) {
+static jboolean nIncLoadSO(JNIEnv *_env, jobject _this, jint deviceApi, jstring libPath) {
     void* handle = NULL;
-    handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+    // For API 9+, dlopen the full path of libRSSupport.
+    if (libPath != NULL) {
+        const char * libPathJni = _env->GetStringUTFChars(libPath, JNI_FALSE);
+        handle = dlopen(libPathJni, RTLD_LAZY | RTLD_LOCAL);
+        _env->ReleaseStringUTFChars(libPath, libPathJni);
+    } else {
+        handle = dlopen("libRSSupport.so", RTLD_LAZY | RTLD_LOCAL);
+    }
+
     if (handle == NULL) {
-        LOG_API("couldn't dlopen %s, %s", filename, dlerror());
+        LOG_ERR("couldn't dlopen %s;  librsjni version: %d", dlerror(), RS_JNI_VERSION);
         return false;
     }
 
     if (loadSymbols(handle, dispatchTabInc, deviceApi) == false) {
-        LOG_API("%s init failed!", filename);
+        LOG_ERR("Dispatch Table init failed! librsjni version: %d", RS_JNI_VERSION);
+        dlclose(handle);
         return false;
     }
-    LOG_API("Successfully loaded %s", filename);
+    dispatchTabInc.AllocationCreateStrided = (AllocationCreateStridedFnPtr)dlsym(handle, "rsAllocationCreateStrided");
+    if (dispatchTabInc.AllocationCreateStrided == NULL) {
+        LOG_ERR("Couldn't initialize dispatchTabInc.AllocationCreateStrided");
+        dlclose(handle);
+        return false;
+    }
+    LOG_API("Successfully loaded compat runtime");
     return true;
 }
 
@@ -1921,7 +2232,7 @@
 // -----------------------------------
 // Create Allocation from pointer
 static jlong
-nIncAllocationCreateTyped(JNIEnv *_env, jobject _this, jlong con, jlong incCon, jlong alloc, jlong type)
+nIncAllocationCreateTyped(JNIEnv *_env, jobject _this, jlong con, jlong incCon, jlong alloc, jlong type, jint xBytesSize)
 {
     LOG_API("nAllocationCreateTyped, con(%p), type(%p), mip(%i), usage(%i), ptr(%p)",
             incCon, (RsElement)type, mips, usage, (void *)pointer);
@@ -1932,21 +2243,77 @@
         pIn = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
                                                RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, 0, 0,
                                                &strideIn, sizeof(size_t));
-        ainI = dispatchTabInc.AllocationCreateTyped((RsContext)incCon, (RsType)type,
-                                                    RS_ALLOCATION_MIPMAP_NONE,
-                                                    RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED,
-                                                    (uintptr_t)pIn);
+        /*
+         * By definition stride is a roundup of xBytesSize with requiredAlignment, so requiredAlignment must
+         * be strictly larger than the difference of (stride - xBytesSize).
+         *
+         * We can prove that as long as requiredAlignment satisfies the following two conditions, the
+         * memory layout will be identical :
+         * 1. Smaller or equal than stride;
+         * 2. Larger than minRequiredAlignment.
+         *
+         * In this case we can simply choose the first power of 2 that satisfies both conditions.
+         */
+        size_t requiredAlignment = 16;
+        size_t minRequiredAlignment = strideIn - xBytesSize;
+        while (requiredAlignment <= minRequiredAlignment) {
+            requiredAlignment <<= 1;
+        }
+        ainI = dispatchTabInc.AllocationCreateStrided((RsContext)incCon, (RsType)type,
+                                                      RS_ALLOCATION_MIPMAP_NONE,
+                                                      RS_ALLOCATION_USAGE_INCREMENTAL_SUPPORT | RS_ALLOCATION_USAGE_SHARED,
+                                                      (uintptr_t)pIn, requiredAlignment);
     }
     return (jlong)(uintptr_t) ainI;
 }
 
+static jobject
+nAllocationGetByteBuffer(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jint xBytesSize, jint dimY, jint dimZ)
+{
+    LOG_API("nAllocationGetByteBuffer, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc);
+    size_t strideIn = xBytesSize;
+    void* ptr = NULL;
+    if (alloc != 0 && dispatchTab.AllocationGetPointer != nullptr) {
+        ptr = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
+                                               RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, dimZ, 0,
+                                               &strideIn, sizeof(size_t));
+    }
+    if (ptr != NULL) {
+        size_t bufferSize = strideIn;
+        if (dimY > 0) {
+            bufferSize *= dimY;
+        }
+        if (dimZ > 0) {
+            bufferSize *= dimZ;
+        }
+        jobject byteBuffer = _env->NewDirectByteBuffer(ptr, (jlong) bufferSize);
+        return byteBuffer;
+    } else {
+        return NULL;
+    }
+}
+
+static jlong
+nAllocationGetStride(JNIEnv *_env, jobject _this, jlong con, jlong alloc)
+{
+    LOG_API("nAllocationGetStride, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc);
+    size_t strideIn;
+    void* ptr = NULL;
+    if (alloc != 0 && dispatchTab.AllocationGetPointer != nullptr) {
+        ptr = dispatchTab.AllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0,
+                                               RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, 0, 0,
+                                               &strideIn, sizeof(size_t));
+    }
+    return (jlong)strideIn;
+}
+
 // ---------------------------------------------------------------------------
 
 
 static const char *classPathName = "android/support/v8/renderscript/RenderScript";
 
 static JNINativeMethod methods[] = {
-{"nLoadSO",                        "(ZI)Z",                                   (bool*)nLoadSO },
+{"nLoadSO",                        "(ZILjava/lang/String;)Z",                 (bool*)nLoadSO },
 {"nLoadIOSO",                      "()Z",                                     (bool*)nLoadIOSO },
 {"nDeviceCreate",                  "()J",                                     (void*)nDeviceCreate },
 {"nDeviceDestroy",                 "(J)V",                                    (void*)nDeviceDestroy },
@@ -2010,8 +2377,11 @@
 {"rsnScriptInvokeV",                 "(JJI[BZ)V",                             (void*)nScriptInvokeV },
 {"rsnScriptForEach",                 "(JJJIJJZ)V",                            (void*)nScriptForEach },
 {"rsnScriptForEach",                 "(JJJIJJ[BZ)V",                          (void*)nScriptForEachV },
+{"rsnScriptForEach",                 "(JJI[JJ[B[I)V",                         (void*)nScriptForEachMulti },
 {"rsnScriptForEachClipped",          "(JJJIJJIIIIIIZ)V",                      (void*)nScriptForEachClipped },
 {"rsnScriptForEachClipped",          "(JJJIJJ[BIIIIIIZ)V",                    (void*)nScriptForEachClippedV },
+{"rsnScriptReduce",                  "(JJIJJ[I)V",                            (void*)nScriptReduce },
+{"rsnScriptReduceNew",               "(JJI[JJ[I)V",                           (void*)nScriptReduceNew },
 {"rsnScriptSetVarI",                 "(JJIIZ)V",                              (void*)nScriptSetVarI },
 {"rsnScriptSetVarJ",                 "(JJIJZ)V",                              (void*)nScriptSetVarJ },
 {"rsnScriptSetVarF",                 "(JJIFZ)V",                              (void*)nScriptSetVarF },
@@ -2044,7 +2414,7 @@
 {"rsnSystemGetPointerSize",          "()I",                                   (void*)nSystemGetPointerSize },
 
 // Entry points for Inc libRSSupport
-{"nIncLoadSO",                       "(I)Z",                                  (bool*)nIncLoadSO },
+{"nIncLoadSO",                       "(ILjava/lang/String;)Z",                (bool*)nIncLoadSO },
 {"nIncDeviceCreate",                 "()J",                                   (void*)nIncDeviceCreate },
 {"nIncDeviceDestroy",                "(J)V",                                  (void*)nIncDeviceDestroy },
 {"rsnIncContextCreate",              "(JIII)J",                               (void*)nIncContextCreate },
@@ -2053,7 +2423,9 @@
 {"rsnIncObjDestroy",                 "(JJ)V",                                 (void*)nIncObjDestroy },
 {"rsnIncElementCreate",              "(JJIZI)J",                              (void*)nIncElementCreate },
 {"rsnIncTypeCreate",                 "(JJIIIZZI)J",                           (void*)nIncTypeCreate },
-{"rsnIncAllocationCreateTyped",      "(JJJJ)J",                               (void*)nIncAllocationCreateTyped },
+{"rsnIncAllocationCreateTyped",      "(JJJJI)J",                              (void*)nIncAllocationCreateTyped },
+{"rsnAllocationGetByteBuffer",       "(JJIII)Ljava/nio/ByteBuffer;",          (void*)nAllocationGetByteBuffer },
+{"rsnAllocationGetStride",           "(JJ)J",                                 (void*)nAllocationGetStride },
 };
 
 // ---------------------------------------------------------------------------
diff --git a/v8/renderscript/rs_support/Android.mk b/v8/renderscript/rs_support/Android.mk
index 2b8f0fe..8e5885a 100644
--- a/v8/renderscript/rs_support/Android.mk
+++ b/v8/renderscript/rs_support/Android.mk
@@ -29,7 +29,7 @@
     rsg_generator.c
 
 LOCAL_CXX_STL := none
-LOCAL_ADDRESS_SANITIZER := false
+LOCAL_SANITIZE := never
 
 include $(BUILD_HOST_EXECUTABLE)
 
@@ -53,8 +53,8 @@
         )
 
 $(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN) : PRIVATE_CUSTOM_TOOL = $(RSG_GENERATOR_SUPPORT) $< $@ <$(PRIVATE_PATH)/rs.spec
-$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec
+$(GEN) : PRIVATE_CUSTOM_TOOL = cat $(PRIVATE_PATH)/rs.spec $(PRIVATE_PATH)/rs_compat.spec | $(RSG_GENERATOR_SUPPORT) $< $@
+$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec $(LOCAL_PATH)/rs_compat.spec
 $(GEN): $(generated_sources_dir)/%.h : $(LOCAL_PATH)/%.h.rsg
 	$(transform-generated-source)
 
@@ -70,8 +70,8 @@
         )
 
 $(GEN) : PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN) : PRIVATE_CUSTOM_TOOL = $(RSG_GENERATOR_SUPPORT) $< $@ <$(PRIVATE_PATH)/rs.spec
-$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec
+$(GEN) : PRIVATE_CUSTOM_TOOL = cat $(PRIVATE_PATH)/rs.spec $(PRIVATE_PATH)/rs_compat.spec | $(RSG_GENERATOR_SUPPORT) $< $@
+$(GEN) : $(RSG_GENERATOR_SUPPORT) $(LOCAL_PATH)/rs.spec $(LOCAL_PATH)/rs_compat.spec
 $(GEN): $(generated_sources_dir)/%.cpp : $(LOCAL_PATH)/%.cpp.rsg
 	$(transform-generated-source)
 
@@ -132,13 +132,12 @@
 	cpu_ref/rsCpuIntrinsicHistogram.cpp \
 	cpu_ref/rsCpuIntrinsicLUT.cpp \
 	cpu_ref/rsCpuIntrinsicResize.cpp \
-	cpu_ref/rsCpuIntrinsicYuvToRGB.cpp \
-	cpu_ref/rsCpuRuntimeMathFuncs.cpp
+	cpu_ref/rsCpuIntrinsicYuvToRGB.cpp
 
 ifeq ($(ARCH_ARM_HAVE_ARMV7A),true)
 LOCAL_CFLAGS_arm := -DARCH_ARM_HAVE_VFP -DARCH_ARM_USE_INTRINSICS
 LOCAL_ASFLAGS_arm := -mfpu=neon
-# frameworks/rs/cpu_ref/rsCpuIntrinsics_neon_3DLUT.S does not compile.
+# Clang does not support nested .irp in *_Blur.S
 LOCAL_CLANG_ASFLAGS_arm += -no-integrated-as
 LOCAL_SRC_FILES_arm := \
         cpu_ref/rsCpuIntrinsics_neon_3DLUT.S \
@@ -151,13 +150,14 @@
 endif
 
 LOCAL_REQUIRED_MODULES := libblasV8
-LOCAL_LDFLAGS += -llog -ldl
-LOCAL_NDK_STL_VARIANT := stlport_static
+LOCAL_STATIC_LIBRARIES := libbnnmlowpV8
+LOCAL_LDFLAGS += -llog -ldl -Wl,--exclude-libs,libc++_static.a
+LOCAL_NDK_STL_VARIANT := c++_static
 
-LOCAL_C_INCLUDES += frameworks/compile/libbcc/include
 LOCAL_C_INCLUDES += external/cblas/include
+LOCAL_C_INCLUDES += external/gemmlowp/eight_bit_int_gemm
 
-LOCAL_CFLAGS += $(rs_base_CFLAGS)
+LOCAL_CFLAGS += $(rs_base_CFLAGS) -DGEMMLOWP_USE_STLPORT
 
 LOCAL_MODULE:= libRSSupport
 LOCAL_MODULE_TAGS := optional